﻿using nVideo.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace nVideo.Codecs.H264
{
    public class SliceDecoder
    {

        private static int[] NULL_VECTOR = new int[] { 0, 0, -1 };
        private SliceHeader sh;
        private CAVLC[] cavlc;
        private CABAC cabac;
        private Mapper mapper;

        private int[] chromaQpOffset;
        private int qp;
        private int[][] leftRow;
        private int[][] topLine;
        private int[][] topLeft;

        private int[] i4x4PredTop;
        private int[] i4x4PredLeft;

        private MBType[] topMBType;
        private MBType leftMBType;
        private ColorSpace chromaFormat;
        private bool transform8x8;

        private int[][][] mvTop;
        private int[][][] mvLeft;
        private int[][] mvTopLeft;
        private SeqParameterSet activeSps;
        private PictureParameterSet activePps;
        private int[][] nCoeff;
        private int[][][][] mvs;
        private MBType[] mbTypes;
        private int[][] mbQps;
        private Frame thisFrame;
        private Frame[] sRefs;
        private Dictionary<int, Frame> lRefs;
        private MDecoder mDecoder;
        private SliceHeader[] shs;

        private int leftCBPLuma;
        private int[] topCBPLuma;

        private int leftCBPChroma;
        private int[] topCBPChroma;
        private int[] numRef;

        private bool tf8x8Left;
        private bool[] tf8x8Top;

        private nVideo.Codecs.H264.H264Const.PartPred[] predModeLeft;
        private nVideo.Codecs.H264.H264Const.PartPred[] predModeTop;
        private bool[] tr8x8Used;
        private Frame[][][] refsUsed;
        private Prediction prediction;
        private bool debug;

        public SliceDecoder(SeqParameterSet activeSps, PictureParameterSet activePps, int[][] nCoeff, int[][][][] mvs,
                MBType[] mbTypes, int[][] mbQps, SliceHeader[] shs, bool[] tr8x8Used, Frame[][][] refsUsed,
                Frame result, Frame[] sRefs, Dictionary<int, Frame> lRefs)
        {

            this.activeSps = activeSps;
            this.activePps = activePps;
            this.nCoeff = nCoeff;
            this.mvs = mvs;
            this.mbTypes = mbTypes;
            this.mbQps = mbQps;
            this.shs = shs;
            this.thisFrame = result;
            this.sRefs = sRefs;
            this.lRefs = lRefs;
            this.tr8x8Used = tr8x8Used;
            this.refsUsed = refsUsed;
        }

        public void decode(MemoryStream segment, NALUnit nalUnit)
        {
            BitReader inb = new BitReader(segment);
            SliceHeaderReader shr = new SliceHeaderReader();
            sh = shr.readPart1(inb);
            sh.sps = activeSps;
            sh.pps = activePps;

            cavlc = new CAVLC[] { new CAVLC(sh.sps, sh.pps, 2, 2), new CAVLC(sh.sps, sh.pps, 1, 1),
                new CAVLC(sh.sps, sh.pps, 1, 1) };

            int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1;
            cabac = new CABAC(mbWidth);

            chromaQpOffset = new int[] { sh.pps.chroma_qp_index_offset,
                sh.pps.extended != null ? sh.pps.extended.second_chroma_qp_index_offset : sh.pps.chroma_qp_index_offset };

            chromaFormat = sh.sps.chroma_format_idc;
            transform8x8 = sh.pps.extended == null ? false : sh.pps.extended.transform_8x8_mode_flag;

            i4x4PredLeft = new int[4];
            i4x4PredTop = new int[mbWidth << 2];
            topMBType = new MBType[mbWidth];

            this.topCBPLuma = new int[mbWidth];
            this.topCBPChroma = new int[mbWidth];

            mvTop = (int[][][])Array.CreateInstance(typeof(int), new int[] { 2, mbWidth << 2 + 1, 3 }); //new int[2][(mbWidth << 2) + 1][3];
            mvLeft = (int[][][])Array.CreateInstance(typeof(int), new int[] { 2, 4, 3 });//new int[2][4][3];
            mvTopLeft = (int[][])Array.CreateInstance(typeof(int), new int[] { 2, 3 });

            leftRow = (int[][])Array.CreateInstance(typeof(int), new int[] { 3, 16 });
            topLeft = (int[][])Array.CreateInstance(typeof(int), new int[] { 3, 4 });
            topLine = (int[][])Array.CreateInstance(typeof(int), new int[] { 3, mbWidth << 4 });//new int[3][mbWidth << 4];

            this.predModeLeft = new nVideo.Codecs.H264.H264Const.PartPred[2];
            this.predModeTop = new nVideo.Codecs.H264.H264Const.PartPred[mbWidth << 1];

            this.tf8x8Top = new bool[mbWidth];

            shr.readPart2(sh, nalUnit, sh.sps, sh.pps, inb);
            prediction = new Prediction(sh);
            qp = sh.pps.pic_init_qp_minus26 + 26 + sh.slice_qp_delta;
            if (activePps.entropy_coding_mode_flag)
            {
                inb.terminate();
                int[][] cm = (int[][])Array.CreateInstance(typeof(int), new int[] { 2, 1024 });//new int[2][1024];
                cabac.initModels(cm, sh.slice_type, sh.cabac_init_idc, qp);
                mDecoder = new MDecoder(segment, cm);
            }

            if (sh.num_ref_idx_active_override_flag)
                numRef = new int[] { sh.num_ref_idx_active_minus1[0] + 1, sh.num_ref_idx_active_minus1[1] + 1 };
            else
                numRef = new int[] { activePps.num_ref_idx_active_minus1[0] + 1, activePps.num_ref_idx_active_minus1[1] + 1 };

            debugPrint("============" + thisFrame.getPOC() + "============= " + sh.slice_type.ToString());

            Frame[][] refList = null;
            if (sh.slice_type == SliceType.P)
            {
                refList = new Frame[][] { buildRefListP(), null };
            }
            else if (sh.slice_type == SliceType.B)
            {
                refList = buildRefListB();
            }

            debugPrint("------");
            if (refList != null)
            {
                for (int l = 0; l < 2; l++)
                {
                    if (refList[l] != null)
                        for (int i = 0; i < refList[l].Length; i++)
                            if (refList[l][i] != null)
                                debugPrint("REF[" + l + "][" + i + "]: " + ((Frame)refList[l][i]).getPOC());
                }
            }

            mapper = new MapManager(sh.sps, sh.pps).getMapper(sh);

            Picture mb = Picture.Create(16, 16, sh.sps.chroma_format_idc);

            bool mbaffFrameFlag = (sh.sps.mb_adaptive_frame_field_flag && !sh.field_pic_flag);

            bool prevMbSkipped = false;
            int ia;
            MBType prevMBType = null;
            for (ia = 0; ; ia++)
            {
                if (sh.slice_type.isInter() && !activePps.entropy_coding_mode_flag)
                {
                    int mbSkipRun = CAVLCReader.readUE(inb, "mb_skip_run");
                    for (int j = 0; j < mbSkipRun; j++, ia++)
                    {
                        int mbAddr = mapper.getAddress(ia);
                        debugPrint("---------------------- MB (" + (mbAddr % mbWidth) + "," + (mbAddr / mbWidth)
                                + ") ---------------------");
                        decodeSkip(refList, ia, mb, sh.slice_type);
                        shs[mbAddr] = sh;
                        refsUsed[mbAddr] = refList;
                        put(thisFrame, mb, mapper.getMbX(ia), mapper.getMbY(ia));
                        wipe(mb);
                    }

                    prevMbSkipped = mbSkipRun > 0;
                    prevMBType = null;

                    if (CAVLCReader.moreRBSPData(inb))
                        break;
                }

                int mbAddra = mapper.getAddress(ia);
                shs[mbAddra] = sh;
                refsUsed[mbAddra] = refList;
                int mbX = mbAddra % mbWidth;
                int mbY = mbAddra / mbWidth;
                debugPrint("---------------------- MB (" + mbX + "," + mbY + ") ---------------------");

                if (sh.slice_type.isIntra()
                        || (!activePps.entropy_coding_mode_flag || !cabac.readMBSkipFlag(mDecoder, sh.slice_type,
                                mapper.leftAvailable(ia), mapper.topAvailable(ia), mbX)))
                {

                    bool mb_field_decoding_flag = false;
                    if (mbaffFrameFlag && (ia % 2 == 0 || (ia % 2 == 1 && prevMbSkipped)))
                    {
                        mb_field_decoding_flag = CAVLCReader.readBool(inb, "mb_field_decoding_flag");
                    }

                    prevMBType = decode(sh.slice_type, ia, inb, mb_field_decoding_flag, prevMBType, mb, refList);

                }
                else
                {
                    decodeSkip(refList, ia, mb, sh.slice_type);
                    prevMBType = null;
                }
                put(thisFrame, mb, mbX, mbY);

                if (activePps.entropy_coding_mode_flag && mDecoder.decodeFinalBin() == 1)
                    break;
                else if (!activePps.entropy_coding_mode_flag && !CAVLCReader.moreRBSPData(inb))
                    break;

                wipe(mb);
            }
        }

        private Frame[] buildRefListP()
        {
            int frame_num = sh.frame_num;
            int maxFrames = 1 << (sh.sps.log2_max_frame_num_minus4 + 4);
            // int nLongTerm = Math.min(lRefs.size(), numRef[0] - 1);
            Frame[] result = new Frame[numRef[0]];

            int refs = 0;
            for (int i = frame_num - 1; i >= frame_num - maxFrames && refs < numRef[0]; i--)
            {
                int fn = i < 0 ? i + maxFrames : i;
                if (sRefs[fn] != null)
                {
                    result[refs] = sRefs[fn] == H264Const.NO_PIC ? null : sRefs[fn];
                    ++refs;
                }
            }
            int[] keys = lRefs.Keys.ToArray();
            Array.Sort(keys);
            for (int i = 0; i < keys.Length && refs < numRef[0]; i++)
            {
                result[refs++] = lRefs[keys[i]];
            }

            reorder(result, 0);

            return result;
        }

        private Frame[][] buildRefListB()
        {

            Frame[] l0 = buildList(Frame.POCDesc, Frame.POCAsc);
            Frame[] l1 = buildList(Frame.POCAsc, Frame.POCDesc);

            if (Array.Equals(l0, l1) && count(l1) > 1)
            {
                Frame frame = l1[1];
                l1[1] = l1[0];
                l1[0] = frame;
            }

            Frame[][] result =  new Frame[][]{
                l0.Take(numRef[0]).ToArray(),
                l1.Take(numRef[1]).ToArray()
            }; //{ Arrays.copyOf(l0, numRef[0]), Arrays.copyOf(l1, numRef[1]) };

            // System.out.println("----------" + thisFrame.getPOC() +
            // "------------");
            // printList("List 0: ", result[0]);
            // printList("List 1: ", result[1]);

            reorder(result[0], 0);
            reorder(result[1], 1);

            // printList("Reorder List 0: ", result[0]);
            // printList("Reorder List 1: ", result[1]);

            return result;
        }

        // private void printList(String label, Frame[] frames) {
        // System.out.print(label);
        // for (Frame frame : frames) {
        // System.out.print(frame.getPOC() + ", ");
        // }
        // System.out.println();
        // }

        private Frame[] buildList(IComparer<Frame> cmpFwd, IComparer<Frame> cmpInv)
        {
            Frame[] refs = new Frame[sRefs.Length + lRefs.Count()];
            Frame[] fwd = copySort(cmpFwd, thisFrame);
            Frame[] inv = copySort(cmpInv, thisFrame);
            int nFwd = count(fwd);
            int nInv = count(inv);

            int irefs = 0;
            for (int i = 0; i < nFwd; i++, irefs++)
                refs[irefs] = fwd[i];
            for (int i = 0; i < nInv; i++, irefs++)
                refs[irefs] = inv[i];

            int[] keys = lRefs.Keys.ToArray();
            Array.Sort(keys);
            for (int i = 0; i < keys.Length; i++, irefs++)
                refs[irefs] = lRefs[keys[i]];

            return refs;
        }

        private int count(Frame[] arr)
        {
            for (int nn = 0; nn < arr.Length; nn++)
                if (arr[nn] == null)
                    return nn;
            return arr.Length;
        }

        private Frame[] copySort(IComparer<Frame> fwd, Frame dummy)
        {
            Frame[] copyOf = sRefs.Take(sRefs.Length).ToArray();
            for (int i = 0; i < copyOf.Length; i++)
                if (fwd.Compare(dummy, copyOf[i]) > 0)
                    copyOf[i] = null;
            Array.Sort(copyOf, fwd);
            return copyOf;
        }

        private void reorder(Picture[] result, int list)
        {
            if (sh.refPicReordering[list] == null)
                return;

            int predict = sh.frame_num;
            int maxFrames = 1 << (sh.sps.log2_max_frame_num_minus4 + 4);

            for (int ind = 0; ind < sh.refPicReordering[list][0].Length; ind++)
            {
                switch (sh.refPicReordering[list][0][ind])
                {
                    case 0:
                        predict = wrap(predict - sh.refPicReordering[list][1][ind] - 1, maxFrames);
                        break;
                    case 1:
                        predict = wrap(predict + sh.refPicReordering[list][1][ind] + 1, maxFrames);
                        break;
                    case 2:
                        throw new Exception("long term");
                }
                for (int i = numRef[list] - 1; i > ind; i--)
                    result[i] = result[i - 1];
                result[ind] = sRefs[predict];
                for (int i = ind + 1, j = i; i < numRef[list] && result[i] != null; i++)
                {
                    if (result[i] != sRefs[predict])
                        result[j++] = result[i];
                }
            }
        }

        public static int wrap(int picNo, int maxFrames)
        {
            return picNo < 0 ? picNo + maxFrames : (picNo >= maxFrames ? picNo - maxFrames : picNo);
        }


        private void wipe(Picture mb)
        {
            Arrays.fill(mb.getPlaneData(0), 0);
            Arrays.fill(mb.getPlaneData(1), 0);
            Arrays.fill(mb.getPlaneData(2), 0);
        }

        private void collectPredictors(Picture outMB, int mbX)
        {
            topLeft[0][0] = topLine[0][(mbX << 4) + 15];
            topLeft[0][1] = outMB.getPlaneData(0)[63];
            topLeft[0][2] = outMB.getPlaneData(0)[127];
            topLeft[0][3] = outMB.getPlaneData(0)[191];
            System.Array.Copy(outMB.getPlaneData(0), 240, topLine[0], mbX << 4, 16);
            copyCol(outMB.getPlaneData(0), 16, 15, 16, leftRow[0]);

            collectChromaPredictors(outMB, mbX);
        }

        private void collectChromaPredictors(Picture outMB, int mbX)
        {
            topLeft[1][0] = topLine[1][(mbX << 3) + 7];
            topLeft[2][0] = topLine[2][(mbX << 3) + 7];

            System.Array.Copy(outMB.getPlaneData(1), 56, topLine[1], mbX << 3, 8);
            System.Array.Copy(outMB.getPlaneData(2), 56, topLine[2], mbX << 3, 8);

            copyCol(outMB.getPlaneData(1), 8, 7, 8, leftRow[1]);
            copyCol(outMB.getPlaneData(2), 8, 7, 8, leftRow[2]);
        }

        private void copyCol(int[] planeData, int n, int off, int stride, int[] outb)
        {
            for (int i = 0; i < n; i++, off += stride)
            {
                outb[i] = planeData[off];
            }
        }

        public MBType decode(SliceType sliceType, int mbAddr, BitReader reader, bool field, MBType prevMbType,
                Picture mb, Frame[][] references)
        {
            if (sliceType == SliceType.I)
            {
                return decodeMBlockI(mbAddr, reader, field, prevMbType, mb);
            }
            else if (sliceType == SliceType.P)
            {
                return decodeMBlockP(mbAddr, reader, field, prevMbType, mb, references);
            }
            else
            {
                return decodeMBlockB(mbAddr, reader, field, prevMbType, mb, references);
            }
        }

        private MBType decodeMBlockI(int mbIdx, BitReader reader, bool field, MBType prevMbType, Picture mb)
        {

            int mbType = 0;
            if (!activePps.entropy_coding_mode_flag)
                mbType = CAVLCReader.readUE(reader, "MB: mb_type");
            else
                mbType = cabac.readMBTypeI(mDecoder, leftMBType, topMBType[mapper.getMbX(mbIdx)],
                        mapper.leftAvailable(mbIdx), mapper.topAvailable(mbIdx));
            return decodeMBlockIInt(mbType, mbIdx, reader, field, prevMbType, mb);
        }

        private MBType decodeMBlockIInt(int mbType, int mbIdx, BitReader reader, bool field, MBType prevMbType,
                Picture mb)
        {
            MBType mbt;
            if (mbType == 0)
            {
                // System.out.println("NxN");
                decodeMBlockIntraNxN(reader, mbIdx, prevMbType, mb);
                mbt = MBType.I_NxN;
            }
            else if (mbType >= 1 && mbType <= 24)
            {
                // System.out.println("16x16");
                mbType--;
                decodeMBlockIntra16x16(reader, mbType, mbIdx, prevMbType, mb);
                mbt = MBType.I_16x16;
            }
            else
            {
                //Logger.warn("IPCM macroblock found. Not tested, may cause unpredictable behavior.");
                decodeMBlockIPCM(reader, mbIdx, mb);
                mbt = MBType.I_PCM;
            }
            int xx = mapper.getMbX(mbIdx) << 2;

            copyVect(mvTopLeft[0], mvTop[0][xx + 3]);
            copyVect(mvTopLeft[1], mvTop[1][xx + 3]);

            saveVect(mvTop[0], xx, xx + 4, 0, 0, -1);
            saveVect(mvLeft[0], 0, 4, 0, 0, -1);
            saveVect(mvTop[1], xx, xx + 4, 0, 0, -1);
            saveVect(mvLeft[1], 0, 4, 0, 0, -1);
            return mbt;
        }

        private MBType decodeMBlockP(int mbIdx, BitReader reader, bool field, MBType prevMbType, Picture mb,
                Frame[][] references)
        {
            int mbType;
            if (!activePps.entropy_coding_mode_flag)
                mbType = CAVLCReader.readUE(reader, "MB: mb_type");
            else
                mbType = cabac.readMBTypeP(mDecoder);

            switch (mbType)
            {
                case 0:
                    decodeInter16x16(reader, mb, references, mbIdx, prevMbType, nVideo.Codecs.H264.H264Const.PartPred.L0, MBType.P_16x16);
                    return MBType.P_16x16;
                case 1:
                    decodeInter16x8(reader, mb, references, mbIdx, prevMbType, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, MBType.P_16x8);
                    return MBType.P_16x8;
                case 2:
                    decodeInter8x16(reader, mb, references, mbIdx, prevMbType, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, MBType.P_8x16);
                    return MBType.P_8x16;
                case 3:
                    decodeMBInter8x8(reader, mbType, references, mb, SliceType.P, mbIdx, field, prevMbType, false);
                    return MBType.P_8x8;
                case 4:
                    decodeMBInter8x8(reader, mbType, references, mb, SliceType.P, mbIdx, field, prevMbType, true);
                    return MBType.P_8x8ref0;
                default:
                    return decodeMBlockIInt(mbType - 5, mbIdx, reader, field, prevMbType, mb);
            }
        }

        private MBType decodeMBlockB(int mbIdx, BitReader reader, bool field, MBType prevMbType, Picture mb,
                Frame[][] references)
        {
            int mbType;
            if (!activePps.entropy_coding_mode_flag)
                mbType = CAVLCReader.readUE(reader, "MB: mb_type");
            else
                mbType = cabac.readMBTypeB(mDecoder, leftMBType, topMBType[mapper.getMbX(mbIdx)],
                        mapper.leftAvailable(mbIdx), mapper.topAvailable(mbIdx));
            if (mbType >= 23)
            {
                return decodeMBlockIInt(mbType - 23, mbIdx, reader, field, prevMbType, mb);
            }
            else
            {
                MBType curMBType = H264Const.bMbTypes[mbType];

                if (mbType == 0)
                    decodeMBBiDirect(mbIdx, reader, field, prevMbType, mb, references);
                else if (mbType <= 3)
                    decodeInter16x16(reader, mb, references, mbIdx, prevMbType, (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPredModes[mbType][0], curMBType);
                else if (mbType == 22)
                    decodeMBInter8x8(reader, mbType, references, mb, SliceType.B, mbIdx, field, prevMbType, false);
                else if ((mbType & 1) == 0)
                    decodeInter16x8(reader, mb, references, mbIdx, prevMbType, (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPredModes[mbType][0],
                            (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPredModes[mbType][1], curMBType);
                else
                    decodeInter8x16(reader, mb, references, mbIdx, prevMbType, (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPredModes[mbType][0],
                            (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPredModes[mbType][1], curMBType);

                return curMBType;
            }
        }

        // TODO: optimize this crap
        public void put(Picture tgt, Picture decoded, int mbX, int mbY)
        {

            int[] luma = tgt.getPlaneData(0);
            int stride = tgt.getPlaneWidth(0);

            int[] cb = tgt.getPlaneData(1);
            int[] cr = tgt.getPlaneData(2);
            int strideChroma = tgt.getPlaneWidth(1);

            int dOff = 0;
            for (int i = 0; i < 16; i++)
            {
                System.Array.Copy(decoded.getPlaneData(0), dOff, luma, (mbY * 16 + i) * stride + mbX * 16, 16);
                dOff += 16;
            }
            for (int i = 0; i < 8; i++)
            {
                System.Array.Copy(decoded.getPlaneData(1), i * 8, cb, (mbY * 8 + i) * strideChroma + mbX * 8, 8);
            }
            for (int i = 0; i < 8; i++)
            {
                System.Array.Copy(decoded.getPlaneData(2), i * 8, cr, (mbY * 8 + i) * strideChroma + mbX * 8, 8);
            }
        }

        public void decodeMBlockIntra16x16(BitReader reader, int mbType, int mbIndex, MBType prevMbType, Picture mb)
        {

            int mbX = mapper.getMbX(mbIndex);
            int mbY = mapper.getMbY(mbIndex);
            int address = mapper.getAddress(mbIndex);

            int cbpChroma = (mbType / 4) % 3;
            int cbpLuma = (mbType / 12) * 15;

            bool leftAvailable = mapper.leftAvailable(mbIndex);
            bool topAvailable = mapper.topAvailable(mbIndex);

            int chromaPredictionMode = readChromaPredMode(reader, mbX, leftAvailable, topAvailable);
            int mbQPDelta = readMBQpDelta(reader, prevMbType);
            qp = (qp + mbQPDelta + 52) % 52;
            mbQps[0][address] = qp;

            residualLumaI16x16(reader, leftAvailable, topAvailable, mbX, mbY, mb, cbpLuma);
            Intra16x16PredictionBuilder.predictWithMode(mbType % 4, mb.getPlaneData(0), leftAvailable, topAvailable,
                    leftRow[0], topLine[0], topLeft[0], mbX << 4);

            decodeChroma(reader, cbpChroma, chromaPredictionMode, mbX, mbY, leftAvailable, topAvailable, mb, qp,
                    MBType.I_16x16);
            mbTypes[address] = topMBType[mbX] = leftMBType = MBType.I_16x16;
            // System.out.println("idx: " + mbIndex + ", addr: " + address);
            topCBPLuma[mbX] = leftCBPLuma = cbpLuma;
            topCBPChroma[mbX] = leftCBPChroma = cbpChroma;
            tf8x8Left = tf8x8Top[mbX] = false;

            collectPredictors(mb, mbX);
            saveMvsIntra(mbX, mbY);
        }

        private int readMBQpDelta(BitReader reader, MBType prevMbType)
        {
            int mbQPDelta;
            if (!activePps.entropy_coding_mode_flag)
            {
                mbQPDelta = CAVLCReader.readSE(reader, "mb_qp_delta");
            }
            else
            {
                mbQPDelta = cabac.readMBQpDelta(mDecoder, prevMbType);
            }
            return mbQPDelta;
        }

        private int readChromaPredMode(BitReader reader, int mbX, bool leftAvailable, bool topAvailable)
        {
            int chromaPredictionMode;
            if (!activePps.entropy_coding_mode_flag)
            {
                chromaPredictionMode = CAVLCReader.readUE(reader, "MBP: intra_chroma_pred_mode");
            }
            else
            {
                chromaPredictionMode = cabac.readIntraChromaPredMode(mDecoder, mbX, leftMBType, topMBType[mbX],
                        leftAvailable, topAvailable);
            }
            return chromaPredictionMode;
        }

        private void residualLumaI16x16(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                Picture mb, int cbpLuma)
        {
            int[] dc = new int[16];
            // System.out.println("=================== 16x16 DC ===========================");
            if (!activePps.entropy_coding_mode_flag)
                cavlc[0].readLumaDCBlock(reader, dc, mbX, leftAvailable, leftMBType, topAvailable, topMBType[mbX],
                        CoeffTransformer.zigzag4x4);
            else
            {
                if (cabac.readCodedBlockFlagLumaDC(mDecoder, mbX, leftMBType, topMBType[mbX], leftAvailable, topAvailable,
                        MBType.I_16x16) == 1)
                    cabac.readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_16_DC, dc, 0, 16, CoeffTransformer.zigzag4x4,
                            H264Const.identityMapping16, H264Const.identityMapping16);
            }

            CoeffTransformer.invDC4x4(dc);
            CoeffTransformer.dequantizeDC4x4(dc, qp);
            CoeffTransformer.reorderDC4x4(dc);

            for (int i = 0; i < 16; i++)
            {
                int[] ac = new int[16];
                int blkOffLeft = H264Const.MB_BLK_OFF_LEFT[i];
                int blkOffTop = H264Const.MB_BLK_OFF_TOP[i];
                int blkX = (mbX << 2) + blkOffLeft;
                int blkY = (mbY << 2) + blkOffTop;

                if ((cbpLuma & (1 << (i >> 2))) != 0)
                {
                    // System.out.println("=================== 16x16 AC ===========================");

                    if (!activePps.entropy_coding_mode_flag)
                    {
                        nCoeff[blkY][blkX] = cavlc[0].readACBlock(reader, ac, blkX, blkOffTop, blkOffLeft != 0
                                || leftAvailable, blkOffLeft == 0 ? leftMBType : MBType.I_16x16, blkOffTop != 0 || topAvailable,
                                blkOffTop == 0 ? topMBType[mbX] : MBType.I_16x16, 1, 15, CoeffTransformer.zigzag4x4);
                    }
                    else
                    {
                        if (cabac.readCodedBlockFlagLumaAC(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_15_AC, blkX, blkOffTop, 0, leftMBType,
                                topMBType[mbX], leftAvailable, topAvailable, leftCBPLuma, topCBPLuma[mbX], cbpLuma,
                                MBType.I_16x16) == 1)
                            nCoeff[blkY][blkX] = cabac.readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_15_AC, ac, 1, 15,
                                    CoeffTransformer.zigzag4x4, H264Const.identityMapping16, H264Const.identityMapping16);
                    }
                    CoeffTransformer.dequantizeAC(ac, qp);
                }
                else
                {
                    if (!activePps.entropy_coding_mode_flag)
                        cavlc[0].setZeroCoeff(blkX, blkOffTop);
                }
                ac[0] = dc[i];
                CoeffTransformer.idct4x4(ac);
                putBlk(mb.getPlaneData(0), ac, 4, blkOffLeft << 2, blkOffTop << 2);
            }
        }

        private void putBlk(int[] planeData, int[] block, int log2stride, int blkX, int blkY)
        {
            int stride = 1 << log2stride;
            for (int line = 0, srcOff = 0, dstOff = (blkY << log2stride) + blkX; line < 4; line++)
            {
                planeData[dstOff] = block[srcOff];
                planeData[dstOff + 1] = block[srcOff + 1];
                planeData[dstOff + 2] = block[srcOff + 2];
                planeData[dstOff + 3] = block[srcOff + 3];
                srcOff += 4;
                dstOff += stride;
            }
        }

        private void putBlk8x8(int[] planeData, int[] block, int log2stride, int blkX, int blkY)
        {
            int stride = 1 << log2stride;
            for (int line = 0, srcOff = 0, dstOff = (blkY << log2stride) + blkX; line < 8; line++)
            {
                for (int row = 0; row < 8; row++)
                    planeData[dstOff + row] = block[srcOff + row];
                srcOff += 8;
                dstOff += stride;
            }
        }

        public void decodeChroma(BitReader reader, int pattern, int chromaMode, int mbX, int mbY, bool leftAvailable,
                bool topAvailable, Picture mb, int qp, MBType curMbType)
        {

            if (chromaFormat == ColorSpace.MONO)
            {
                Arrays.fill(mb.getPlaneData(1), 128);
                Arrays.fill(mb.getPlaneData(2), 128);
                return;
            }

            int qp1 = calcQpChroma(qp, chromaQpOffset[0]);
            int qp2 = calcQpChroma(qp, chromaQpOffset[1]);
            if (pattern != 0)
            {
                decodeChromaResidual(reader, leftAvailable, topAvailable, mbX, mbY, pattern, mb, qp1, qp2, curMbType);
            }
            else if (!activePps.entropy_coding_mode_flag)
            {
                cavlc[1].setZeroCoeff(mbX << 1, 0);
                cavlc[1].setZeroCoeff((mbX << 1) + 1, 1);
                cavlc[2].setZeroCoeff(mbX << 1, 0);
                cavlc[2].setZeroCoeff((mbX << 1) + 1, 1);
            }
            int addr = mbY * (activeSps.pic_width_in_mbs_minus1 + 1) + mbX;
            mbQps[1][addr] = qp1;
            mbQps[2][addr] = qp2;
            ChromaPredictionBuilder.predictWithMode(mb.getPlaneData(1), chromaMode, mbX, leftAvailable, topAvailable,
                    leftRow[1], topLine[1], topLeft[1]);
            ChromaPredictionBuilder.predictWithMode(mb.getPlaneData(2), chromaMode, mbX, leftAvailable, topAvailable,
                    leftRow[2], topLine[2], topLeft[2]);
        }

        private void decodeChromaResidual(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                int pattern, Picture mb, int crQp1, int crQp2, MBType curMbType)
        {
            int[] dc1 = new int[(16 >> chromaFormat.compWidth[1]) >> chromaFormat.compHeight[1]];
            int[] dc2 = new int[(16 >> chromaFormat.compWidth[2]) >> chromaFormat.compHeight[2]];
            if ((pattern & 3) > 0)
            {
                chromaDC(reader, mbX, leftAvailable, topAvailable, dc1, 1, crQp1, curMbType);
                chromaDC(reader, mbX, leftAvailable, topAvailable, dc2, 2, crQp2, curMbType);
            }
            chromaAC(reader, leftAvailable, topAvailable, mbX, mbY, mb, dc1, 1, crQp1, curMbType, (pattern & 2) > 0);
            chromaAC(reader, leftAvailable, topAvailable, mbX, mbY, mb, dc2, 2, crQp2, curMbType, (pattern & 2) > 0);
        }

        private void chromaDC(BitReader reader, int mbX, bool leftAvailable, bool topAvailable, int[] dc, int comp,
                int crQp, MBType curMbType)
        {
            // System.out.println("============= CHROMA RESIDUAL DC ================");
            if (!activePps.entropy_coding_mode_flag)
                cavlc[comp].readChromaDCBlock(reader, dc, leftAvailable, topAvailable);
            else
            {
                if (cabac.readCodedBlockFlagChromaDC(mDecoder, mbX, comp, leftMBType, topMBType[mbX], leftAvailable,
                        topAvailable, leftCBPChroma, topCBPChroma[mbX], curMbType) == 1)
                    cabac.readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.CHROMA_DC, dc, 0, 4, H264Const.identityMapping16, H264Const.identityMapping16,
                            H264Const.identityMapping16);
            }

            CoeffTransformer.invDC2x2(dc);
            CoeffTransformer.dequantizeDC2x2(dc, crQp);
        }

        private void chromaAC(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY, Picture mb,
                int[] dc, int comp, int crQp, MBType curMbType, bool codedAC)
        {
            // System.out.println("============= CHROMA RESIDUAL AC ================");
            for (int i = 0; i < dc.Length; i++)
            {
                int[] ac = new int[16];
                int blkOffLeft = H264Const.MB_BLK_OFF_LEFT[i];
                int blkOffTop = H264Const.MB_BLK_OFF_TOP[i];

                int blkX = (mbX << 1) + blkOffLeft;
                int blkY = (mbY << 1) + blkOffTop;

                if (codedAC)
                {

                    if (!activePps.entropy_coding_mode_flag)
                        cavlc[comp].readACBlock(reader, ac, blkX, blkOffTop, blkOffLeft != 0 || leftAvailable,
                                blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0 || topAvailable,
                                blkOffTop == 0 ? topMBType[mbX] : curMbType, 1, 15, CoeffTransformer.zigzag4x4);
                    else
                    {
                        if (cabac.readCodedBlockFlagChromaAC(mDecoder, blkX, blkOffTop, comp, leftMBType, topMBType[mbX],
                                leftAvailable, topAvailable, leftCBPChroma, topCBPChroma[mbX], curMbType) == 1)
                            cabac.readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.CHROMA_AC, ac, 1, 15, CoeffTransformer.zigzag4x4,
                                    H264Const.identityMapping16, H264Const.identityMapping16);
                    }
                    CoeffTransformer.dequantizeAC(ac, crQp);
                }
                else
                {
                    if (!activePps.entropy_coding_mode_flag)
                        cavlc[comp].setZeroCoeff(blkX, blkOffTop);
                }
                ac[0] = dc[i];
                CoeffTransformer.idct4x4(ac);
                putBlk(mb.getPlaneData(comp), ac, 3, blkOffLeft << 2, blkOffTop << 2);
            }
        }

        private int calcQpChroma(int qp, int crQpOffset)
        {
            return H264Const.QP_SCALE_CR[CABAC.clip(qp + crQpOffset, 0, 51)];
        }

        public void decodeMBlockIntraNxN(BitReader reader, int mbIndex, MBType prevMbType, Picture mb)
        {
            int mbX = mapper.getMbX(mbIndex);
            int mbY = mapper.getMbY(mbIndex);
            int mbAddr = mapper.getAddress(mbIndex);
            bool leftAvailable = mapper.leftAvailable(mbIndex);
            bool topAvailable = mapper.topAvailable(mbIndex);
            bool topLeftAvailable = mapper.topLeftAvailable(mbIndex);
            bool topRightAvailable = mapper.topRightAvailable(mbIndex);

            bool transform8x8Used = false;
            if (transform8x8)
            {
                transform8x8Used = readTransform8x8Flag(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                        tf8x8Left, tf8x8Top[mbX]);
            }

            int[] lumaModes;
            if (!transform8x8Used)
            {
                lumaModes = new int[16];
                for (int i = 0; i < 16; i++)
                {
                    int blkX = H264Const.MB_BLK_OFF_LEFT[i];
                    int blkY = H264Const.MB_BLK_OFF_TOP[i];
                    lumaModes[i] = readPredictionI4x4Block(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            blkX, blkY, mbX);
                }
            }
            else
            {
                lumaModes = new int[4];
                for (int i = 0; i < 4; i++)
                {
                    int blkX = (i & 1) << 1;
                    int blkY = i & 2;
                    lumaModes[i] = readPredictionI4x4Block(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            blkX, blkY, mbX);
                    i4x4PredLeft[blkY + 1] = i4x4PredLeft[blkY];
                    i4x4PredTop[(mbX << 2) + blkX + 1] = i4x4PredTop[(mbX << 2) + blkX];
                }
            }
            int chromaMode = readChromaPredMode(reader, mbX, leftAvailable, topAvailable);

            int codedBlockPattern = readCodedBlockPatternIntra(reader, leftAvailable, topAvailable, leftCBPLuma
                    | (leftCBPChroma << 4), topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]);

            int cbpLuma = codedBlockPattern & 0xf;
            int cbpChroma = codedBlockPattern >> 4;

            if (cbpLuma > 0 || cbpChroma > 0)
            {
                qp = (qp + readMBQpDelta(reader, prevMbType) + 52) % 52;
            }
            mbQps[0][mbAddr] = qp;

            residualLuma(reader, leftAvailable, topAvailable, mbX, mbY, mb, codedBlockPattern, MBType.I_NxN,
                    transform8x8Used, tf8x8Left, tf8x8Top[mbX]);

            if (!transform8x8Used)
            {
                for (int i = 0; i < 16; i++)
                {
                    int blkX = (i & 3) << 2;
                    int blkY = i & ~3;

                    int bi = H264Const.BLK_INV_MAP[i];
                    bool trAvailable = ((bi == 0 || bi == 1 || bi == 4) && topAvailable)
                            || (bi == 5 && topRightAvailable) || bi == 2 || bi == 6 || bi == 8 || bi == 9 || bi == 10
                            || bi == 12 || bi == 14;

                    Intra4x4PredictionBuilder.predictWithMode(lumaModes[bi], mb.getPlaneData(0), blkX == 0 ? leftAvailable
                            : true, blkY == 0 ? topAvailable : true, trAvailable, leftRow[0], topLine[0], topLeft[0],
                            (mbX << 4), blkX, blkY);
                }
            }
            else
            {
                for (int i = 0; i < 4; i++)
                {
                    int blkX = (i & 1) << 1;
                    int blkY = i & 2;

                    bool trAvailable = (i == 0 && topAvailable) || (i == 1 && topRightAvailable) || i == 2;
                    bool tlAvailable = i == 0 ? topLeftAvailable : (i == 1 ? topAvailable : (i == 2 ? leftAvailable
                            : true));

                    Intra8x8PredictionBuilder.predictWithMode(lumaModes[i], mb.getPlaneData(0), blkX == 0 ? leftAvailable
                            : true, blkY == 0 ? topAvailable : true, tlAvailable, trAvailable, leftRow[0], topLine[0],
                            topLeft[0], (mbX << 4), blkX << 2, blkY << 2);
                }
            }

            decodeChroma(reader, cbpChroma, chromaMode, mbX, mbY, leftAvailable, topAvailable, mb, qp, MBType.I_NxN);

            mbTypes[mbAddr] = topMBType[mbX] = leftMBType = MBType.I_NxN;
            // System.out.println("idx: " + mbIndex + ", addr: " + address);
            topCBPLuma[mbX] = leftCBPLuma = cbpLuma;
            topCBPChroma[mbX] = leftCBPChroma = cbpChroma;
            tf8x8Left = tf8x8Top[mbX] = transform8x8Used;
            tr8x8Used[mbAddr] = transform8x8Used;

            collectChromaPredictors(mb, mbX);

            saveMvsIntra(mbX, mbY);
        }

        private void saveMvsIntra(int mbX, int mbY)
        {
            for (int j = 0, blkOffY = mbY << 2, blkInd = 0; j < 4; j++, blkOffY++)
            {
                for (int i = 0, blkOffX = mbX << 2; i < 4; i++, blkOffX++, blkInd++)
                {
                    mvs[0][blkOffY][blkOffX] = NULL_VECTOR;
                    mvs[1][blkOffY][blkOffX] = NULL_VECTOR;
                }
            }
        }

        private void residualLuma(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                Picture mb, int codedBlockPattern, MBType mbType, bool transform8x8Used, bool is8x8Left,
                bool is8x8Top)
        {
            if (!transform8x8Used)
                residualLuma(reader, leftAvailable, topAvailable, mbX, mbY, mb, codedBlockPattern, mbType);
            else if (activePps.entropy_coding_mode_flag)
                residualLuma8x8CABAC(reader, leftAvailable, topAvailable, mbX, mbY, mb, codedBlockPattern, mbType,
                        is8x8Left, is8x8Top);
            else
                residualLuma8x8CAVLC(reader, leftAvailable, topAvailable, mbX, mbY, mb, codedBlockPattern, mbType);
        }

        private bool readTransform8x8Flag(BitReader reader, bool leftAvailable, bool topAvailable,
                MBType leftType, MBType topType, bool is8x8Left, bool is8x8Top)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readBool(reader, "transform_size_8x8_flag");
            else
                return cabac.readTransform8x8Flag(mDecoder, leftAvailable, topAvailable, leftType, topType, is8x8Left,
                        is8x8Top);
        }

        protected int readCodedBlockPatternIntra(BitReader reader, bool leftAvailable, bool topAvailable,
                int leftCBP, int topCBP, MBType leftMB, MBType topMB)
        {

            if (!activePps.entropy_coding_mode_flag)
                return H264Const.CODED_BLOCK_PATTERN_INTRA_COLOR[CAVLCReader.readUE(reader, "coded_block_pattern")];
            else
                return cabac.codedBlockPatternIntra(mDecoder, leftAvailable, topAvailable, leftCBP, topCBP, leftMB, topMB);
        }

        protected int readCodedBlockPatternInter(BitReader reader, bool leftAvailable, bool topAvailable,
                int leftCBP, int topCBP, MBType leftMB, MBType topMB)
        {
            if (!activePps.entropy_coding_mode_flag)
                return H264Const.CODED_BLOCK_PATTERN_INTER_COLOR[CAVLCReader.readUE(reader, "coded_block_pattern")];
            else
                return cabac.codedBlockPatternIntra(mDecoder, leftAvailable, topAvailable, leftCBP, topCBP, leftMB, topMB);
        }

        private void residualLuma(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                Picture mb, int codedBlockPattern, MBType curMbType)
        {

            int cbpLuma = codedBlockPattern & 0xf;

            for (int i = 0; i < 16; i++)
            {
                int blkOffLeft = H264Const.MB_BLK_OFF_LEFT[i];
                int blkOffTop = H264Const.MB_BLK_OFF_TOP[i];
                int blkX = (mbX << 2) + blkOffLeft;
                int blkY = (mbY << 2) + blkOffTop;

                if ((cbpLuma & (1 << (i >> 2))) == 0)
                {
                    if (!activePps.entropy_coding_mode_flag)
                        cavlc[0].setZeroCoeff(blkX, blkOffTop);
                    continue;
                }
                int[] ac = new int[16];

                if (!activePps.entropy_coding_mode_flag)
                {
                    nCoeff[blkY][blkX] = cavlc[0].readACBlock(reader, ac, blkX, blkOffTop,
                            blkOffLeft != 0 || leftAvailable, blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0
                                    || topAvailable, blkOffTop == 0 ? topMBType[mbX] : curMbType, 0, 16,
                            CoeffTransformer.zigzag4x4);
                }
                else
                {
                    if (cabac.readCodedBlockFlagLumaAC(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_16, blkX, blkOffTop, 0, leftMBType,
                            topMBType[mbX], leftAvailable, topAvailable, leftCBPLuma, topCBPLuma[mbX], cbpLuma, curMbType) == 1)
                        nCoeff[blkY][blkX] = cabac.readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_16, ac, 0, 16,
                                CoeffTransformer.zigzag4x4, H264Const.identityMapping16, H264Const.identityMapping16);
                }

                CoeffTransformer.dequantizeAC(ac, qp);
                CoeffTransformer.idct4x4(ac);
                putBlk(mb.getPlaneData(0), ac, 4, blkOffLeft << 2, blkOffTop << 2);
            }

            if (activePps.entropy_coding_mode_flag)
                cabac.setPrevCBP(codedBlockPattern);
        }

        private void residualLuma8x8CABAC(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                Picture mb, int codedBlockPattern, MBType curMbType, bool is8x8Left, bool is8x8Top)
        {

            // System.out.println("8x8 CABAC!!!");

            int cbpLuma = codedBlockPattern & 0xf;

            for (int i = 0; i < 4; i++)
            {
                int blkOffLeft = (i & 1) << 1;
                int blkOffTop = i & 2;
                int blkX = (mbX << 2) + blkOffLeft;
                int blkY = (mbY << 2) + blkOffTop;

                if ((cbpLuma & (1 << i)) == 0)
                {
                    continue;
                }
                int[] ac = new int[64];

                // System.out.println("============= CABAC RESIDUAL AC ================");
                nCoeff[blkY][blkX] = nCoeff[blkY][blkX + 1] = nCoeff[blkY + 1][blkX] = nCoeff[blkY + 1][blkX + 1] = cabac
                        .readCoeffs(mDecoder, nVideo.Codecs.H264.CABAC.BlockType.LUMA_64, ac, 0, 64, CoeffTransformer.zigzag8x8, H264Const.sig_coeff_map_8x8,
                                H264Const.last_sig_coeff_map_8x8);
                cabac.setCodedBlock(blkX, blkY);
                cabac.setCodedBlock(blkX + 1, blkY);
                cabac.setCodedBlock(blkX, blkY + 1);
                cabac.setCodedBlock(blkX + 1, blkY + 1);

                CoeffTransformer.dequantizeAC8x8(ac, qp);
                CoeffTransformer.idct8x8(ac);
                putBlk8x8(mb.getPlaneData(0), ac, 4, blkOffLeft << 2, blkOffTop << 2);
            }

            cabac.setPrevCBP(codedBlockPattern);
        }

        private void residualLuma8x8CAVLC(BitReader reader, bool leftAvailable, bool topAvailable, int mbX, int mbY,
                Picture mb, int codedBlockPattern, MBType curMbType)
        {

            // System.out.println("8x8 CAVLC!!!");

            int cbpLuma = codedBlockPattern & 0xf;

            for (int i = 0; i < 4; i++)
            {
                int blk8x8OffLeft = (i & 1) << 1;
                int blk8x8OffTop = i & 2;
                int blkX = (mbX << 2) + blk8x8OffLeft;
                int blkY = (mbY << 2) + blk8x8OffTop;

                if ((cbpLuma & (1 << i)) == 0)
                {
                    cavlc[0].setZeroCoeff(blkX, blk8x8OffTop);
                    cavlc[0].setZeroCoeff(blkX + 1, blk8x8OffTop);
                    cavlc[0].setZeroCoeff(blkX, blk8x8OffTop + 1);
                    cavlc[0].setZeroCoeff(blkX + 1, blk8x8OffTop + 1);
                    continue;
                }
                int[] ac64 = new int[64];
                int coeffs = 0;
                for (int j = 0; j < 4; j++)
                {
                    int[] ac16 = new int[16];
                    int blkOffLeft = blk8x8OffLeft + (j & 1);
                    int blkOffTop = blk8x8OffTop + (j >> 1);
                    coeffs += cavlc[0].readACBlock(reader, ac16, blkX + (j & 1), blkOffTop, blkOffLeft != 0
                            || leftAvailable, blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0 || topAvailable,
                            blkOffTop == 0 ? topMBType[mbX] : curMbType, 0, 16, H264Const.identityMapping16);
                    for (int k = 0; k < 16; k++)
                        ac64[CoeffTransformer.zigzag8x8[(k << 2) + j]] = ac16[k];
                }
                nCoeff[blkY][blkX] = nCoeff[blkY][blkX + 1] = nCoeff[blkY + 1][blkX] = nCoeff[blkY + 1][blkX + 1] = coeffs;

                CoeffTransformer.dequantizeAC8x8(ac64, qp);
                CoeffTransformer.idct8x8(ac64);
                putBlk8x8(mb.getPlaneData(0), ac64, 4, blk8x8OffLeft << 2, blk8x8OffTop << 2);
            }
        }

        private int readPredictionI4x4Block(BitReader reader, bool leftAvailable, bool topAvailable,
                MBType leftMBType, MBType topMBType, int blkX, int blkY, int mbX)
        {
            int mode = 2;
            if ((leftAvailable || blkX > 0) && (topAvailable || blkY > 0))
            {
                int predModeB = topMBType == MBType.I_NxN || blkY > 0 ? i4x4PredTop[(mbX << 2) + blkX] : 2;
                int predModeA = leftMBType == MBType.I_NxN || blkX > 0 ? i4x4PredLeft[blkY] : 2;
                mode = Math.Min(predModeB, predModeA);
            }
            if (!prev4x4PredMode(reader))
            {
                int rem_intra4x4_pred_mode = rem4x4PredMode(reader);
                mode = rem_intra4x4_pred_mode + (rem_intra4x4_pred_mode < mode ? 0 : 1);
            }
            i4x4PredTop[(mbX << 2) + blkX] = i4x4PredLeft[blkY] = mode;
            return mode;
        }

        private int rem4x4PredMode(BitReader reader)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readNBit(reader, 3, "MB: rem_intra4x4_pred_mode");
            else
                return cabac.rem4x4PredMode(mDecoder);
        }

        private bool prev4x4PredMode(BitReader reader)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readBool(reader, "MBP: prev_intra4x4_pred_mode_flag");
            else
                return cabac.prev4x4PredModeFlag(mDecoder);
        }

        private void decodeInter16x8(BitReader reader, Picture mb, Frame[][] refs, int mbIdx, MBType prevMbType,
                nVideo.Codecs.H264.H264Const.PartPred p0, nVideo.Codecs.H264.H264Const.PartPred p1, MBType curMBType)
        {
            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            bool leftAvailable = mapper.leftAvailable(mbIdx);
            bool topAvailable = mapper.topAvailable(mbIdx);
            bool topLeftAvailable = mapper.topLeftAvailable(mbIdx);
            bool topRightAvailable = mapper.topRightAvailable(mbIdx);
            int address = mapper.getAddress(mbIdx);

            int xx = mbX << 2;
            int[] refIdx1 = { 0, 0 }, refIdx2 = { 0, 0 };
            int[][][] x = new int[2][][];

            for (int list = 0; list < 2; list++)
            {
                if (p0.usesList(list) && numRef[list] > 1)
                    refIdx1[list] = readRefIdx(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            predModeLeft[0], predModeTop[(mbX << 1)], p0, mbX, 0, 0, 4, 2, list);
                if (p1.usesList(list) && numRef[list] > 1)
                    refIdx2[list] = readRefIdx(reader, leftAvailable, true, leftMBType, curMBType, predModeLeft[1], p0, p1,
                            mbX, 0, 2, 4, 2, list);
            }

            Picture[] mbb = { Picture.Create(16, 16, chromaFormat), Picture.Create(16, 16, chromaFormat) };
            for (int list = 0; list < 2; list++)
            {
                predictInter16x8(reader, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable,
                        topRightAvailable, xx, refIdx1, refIdx2, x, p0, p1, list);
            }

            prediction.mergePrediction(x[0][0][2], x[1][0][2], p0, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), 0,
                    16, 16, 8, mb.getPlaneData(0), refs, thisFrame);
            prediction.mergePrediction(x[0][8][2], x[1][8][2], p1, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), 128,
                    16, 16, 8, mb.getPlaneData(0), refs, thisFrame);

            predModeLeft[0] = p0;
            predModeLeft[1] = predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = p1;

            residualInter(reader, mb, refs, leftAvailable, topAvailable, mbX, mbY, x, new nVideo.Codecs.H264.H264Const.PartPred[] { p0, p0, p1, p1 },
                    mapper.getAddress(mbIdx), prevMbType, curMBType);

            collectPredictors(mb, mbX);

            mbTypes[address] = topMBType[mbX] = leftMBType = curMBType;
        }

        private void predictInter16x8(BitReader reader, Picture mb, Picture[][] references, int mbX, int mbY,
                bool leftAvailable, bool topAvailable, bool tlAvailable, bool trAvailable, int xx,
                int[] refIdx1, int[] refIdx2, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred p0, nVideo.Codecs.H264.H264Const.PartPred p1, int list)
        {

            int blk8x8X = mbX << 1;
            int mvX1 = 0, mvY1 = 0, mvX2 = 0, mvY2 = 0, r1 = -1, r2 = -1;
            if (p0.usesList(list))
            {

                int mvdX1 = readMVD(reader, 0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], p0, mbX, 0, 0, 4, 2, list);
                int mvdY1 = readMVD(reader, 1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], p0, mbX, 0, 0, 4, 2, list);

                int mvpX1 = calcMVPrediction16x8Top(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 4],
                        mvTopLeft[list], leftAvailable, topAvailable, trAvailable, tlAvailable, refIdx1[list], 0);
                int mvpY1 = calcMVPrediction16x8Top(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 4],
                        mvTopLeft[list], leftAvailable, topAvailable, trAvailable, tlAvailable, refIdx1[list], 1);

                mvX1 = mvdX1 + mvpX1;
                mvY1 = mvdY1 + mvpY1;

                debugPrint("MVP: (" + mvpX1 + ", " + mvpY1 + "), MVD: (" + mvdX1 + ", " + mvdY1 + "), MV: (" + mvX1 + ","
                        + mvY1 + "," + refIdx1[list] + ")");

                BlockInterpolator.getBlockLuma(references[list][refIdx1[list]], mb, 0, (mbX << 6) + mvX1,
                        (mbY << 6) + mvY1, 16, 8);
                r1 = refIdx1[list];
            }
            int[] v1 = { mvX1, mvY1, r1 };

            if (p1.usesList(list))
            {
                int mvdX2 = readMVD(reader, 0, leftAvailable, true, leftMBType, MBType.P_16x8, predModeLeft[1], p0, p1,
                        mbX, 0, 2, 4, 2, list);
                int mvdY2 = readMVD(reader, 1, leftAvailable, true, leftMBType, MBType.P_16x8, predModeLeft[1], p0, p1,
                        mbX, 0, 2, 4, 2, list);

                int mvpX2 = calcMVPrediction16x8Bottom(mvLeft[list][2], v1, null, mvLeft[list][1], leftAvailable, true,
                        false, leftAvailable, refIdx2[list], 0);
                int mvpY2 = calcMVPrediction16x8Bottom(mvLeft[list][2], v1, null, mvLeft[list][1], leftAvailable, true,
                        false, leftAvailable, refIdx2[list], 1);

                mvX2 = mvdX2 + mvpX2;
                mvY2 = mvdY2 + mvpY2;

                debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mvdX2 + ", " + mvdY2 + "), MV: (" + mvX2 + ","
                        + mvY2 + "," + refIdx2[list] + ")");

                BlockInterpolator.getBlockLuma(references[list][refIdx2[list]], mb, 128, (mbX << 6) + mvX2, (mbY << 6) + 32
                        + mvY2, 16, 8);
                r2 = refIdx2[list];
            }
            int[] v2 = { mvX2, mvY2, r2 };

            copyVect(mvTopLeft[list], mvTop[list][xx + 3]);
            saveVect(mvLeft[list], 0, 2, mvX1, mvY1, r1);
            saveVect(mvLeft[list], 2, 4, mvX2, mvY2, r2);
            saveVect(mvTop[list], xx, xx + 4, mvX2, mvY2, r2);

            x[list] = new int[][] { v1, v1, v1, v1, v1, v1, v1, v1, v2, v2, v2, v2, v2, v2, v2, v2 };
        }

        private void residualInter(BitReader reader, Picture mb, Frame[][] refs, bool leftAvailable,
                bool topAvailable, int mbX, int mbY, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] pp, int mbAddr, MBType prevMbType,
                MBType curMbType)
        {
            int codedBlockPattern = readCodedBlockPatternInter(reader, leftAvailable, topAvailable, leftCBPLuma
                    | (leftCBPChroma << 4), topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]);
            int cbpLuma = codedBlockPattern & 0xf;
            int cbpChroma = codedBlockPattern >> 4;

            Picture mb1 = Picture.Create(16, 16, chromaFormat);

            bool transform8x8Used = false;
            if (cbpLuma != 0 && transform8x8)
            {
                transform8x8Used = readTransform8x8Flag(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                        tf8x8Left, tf8x8Top[mbX]);
            }

            if (cbpLuma > 0 || cbpChroma > 0)
            {
                int mbQpDelta = readMBQpDelta(reader, prevMbType);
                qp = (qp + mbQpDelta + 52) % 52;
            }
            mbQps[0][mbAddr] = qp;

            residualLuma(reader, leftAvailable, topAvailable, mbX, mbY, mb1, codedBlockPattern, curMbType,
                    transform8x8Used, tf8x8Left, tf8x8Top[mbX]);

            saveMvs(x, mbX, mbY);

            if (chromaFormat == ColorSpace.MONO)
            {
                Arrays.fill(mb.getPlaneData(1), 128);
                Arrays.fill(mb.getPlaneData(2), 128);
            }
            else
            {
                decodeChromaInter(reader, cbpChroma, refs, x, pp, leftAvailable, topAvailable, mbX, mbY, mbAddr, qp, mb,
                        mb1);
            }

            mergeResidual(mb, mb1);

            // System.out.println("idx: " + mbIndex + ", addr: " + address);
            topCBPLuma[mbX] = leftCBPLuma = cbpLuma;
            topCBPChroma[mbX] = leftCBPChroma = cbpChroma;
            tf8x8Left = tf8x8Top[mbX] = transform8x8Used;
            tr8x8Used[mbAddr] = transform8x8Used;
        }

        private void mergeResidual(Picture mb, Picture mb1)
        {
            for (int j = 0; j < 3; j++)
            {
                int[] to = mb.getPlaneData(j), from = mb1.getPlaneData(j);
                for (int i = 0; i < to.Length; i++)
                {
                    to[i] = CABAC.clip(to[i] + from[i], 0, 255);
                }
            }
        }

        private void decodeInter8x16(BitReader reader, Picture mb, Frame[][] refs, int mbIdx, MBType prevMbType,
                nVideo.Codecs.H264.H264Const.PartPred p0, nVideo.Codecs.H264.H264Const.PartPred p1, MBType curMBType)
        {
            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            bool leftAvailable = mapper.leftAvailable(mbIdx);
            bool topAvailable = mapper.topAvailable(mbIdx);
            bool topLeftAvailable = mapper.topLeftAvailable(mbIdx);
            bool topRightAvailable = mapper.topRightAvailable(mbIdx);
            int address = mapper.getAddress(mbIdx);

            int[][][] x = new int[2][][];

            int[] refIdx1 = { 0, 0 }, refIdx2 = { 0, 0 };
            for (int list = 0; list < 2; list++)
            {
                if (p0.usesList(list) && numRef[list] > 1)
                    refIdx1[list] = readRefIdx(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            predModeLeft[0], predModeTop[mbX << 1], p0, mbX, 0, 0, 2, 4, list);
                if (p1.usesList(list) && numRef[list] > 1)
                    refIdx2[list] = readRefIdx(reader, true, topAvailable, curMBType, topMBType[mbX], p0,
                            predModeTop[(mbX << 1) + 1], p1, mbX, 2, 0, 2, 4, list);
            }

            Picture[] mbb = { Picture.Create(16, 16, chromaFormat), Picture.Create(16, 16, chromaFormat) };

            for (int list = 0; list < 2; list++)
            {
                predictInter8x16(reader, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable,
                        topRightAvailable, x, refIdx1, refIdx2, list, p0, p1);
            }

            prediction.mergePrediction(x[0][0][2], x[1][0][2], p0, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), 0,
                    16, 8, 16, mb.getPlaneData(0), refs, thisFrame);
            prediction.mergePrediction(x[0][2][2], x[1][2][2], p1, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), 8,
                    16, 8, 16, mb.getPlaneData(0), refs, thisFrame);

            predModeTop[mbX << 1] = p0;
            predModeTop[(mbX << 1) + 1] = predModeLeft[0] = predModeLeft[1] = p1;

            residualInter(reader, mb, refs, leftAvailable, topAvailable, mbX, mbY, x, new nVideo.Codecs.H264.H264Const.PartPred[] { p0, p1, p0, p1 },
                    mapper.getAddress(mbIdx), prevMbType, curMBType);

            collectPredictors(mb, mbX);

            mbTypes[address] = topMBType[mbX] = leftMBType = curMBType;
        }

        private void predictInter8x16(BitReader reader, Picture mb, Picture[][] references, int mbX, int mbY,
                bool leftAvailable, bool topAvailable, bool tlAvailable, bool trAvailable, int[][][] x,
                int[] refIdx1, int[] refIdx2, int list, nVideo.Codecs.H264.H264Const.PartPred p0, nVideo.Codecs.H264.H264Const.PartPred p1)
        {
            int xx = mbX << 2;

            int blk8x8X = (mbX << 1);

            int mvX1 = 0, mvY1 = 0, r1 = -1, mvX2 = 0, mvY2 = 0, r2 = -1;
            if (p0.usesList(list))
            {
                int mvdX1 = readMVD(reader, 0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], p0, mbX, 0, 0, 2, 4, list);
                int mvdY1 = readMVD(reader, 1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], p0, mbX, 0, 0, 2, 4, list);

                int mvpX1 = calcMVPrediction8x16Left(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 2],
                        mvTopLeft[list], leftAvailable, topAvailable, topAvailable, tlAvailable, refIdx1[list], 0);
                int mvpY1 = calcMVPrediction8x16Left(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 2],
                        mvTopLeft[list], leftAvailable, topAvailable, topAvailable, tlAvailable, refIdx1[list], 1);

                mvX1 = mvdX1 + mvpX1;
                mvY1 = mvdY1 + mvpY1;

                debugPrint("MVP: (" + mvpX1 + ", " + mvpY1 + "), MVD: (" + mvdX1 + ", " + mvdY1 + "), MV: (" + mvX1 + ","
                        + mvY1 + "," + refIdx1[list] + ")");

                BlockInterpolator.getBlockLuma(references[list][refIdx1[list]], mb, 0, (mbX << 6) + mvX1,
                        (mbY << 6) + mvY1, 8, 16);
                r1 = refIdx1[list];
            }
            int[] v1 = { mvX1, mvY1, r1 };

            if (p1.usesList(list))
            {
                int mvdX2 = readMVD(reader, 0, true, topAvailable, MBType.P_8x16, topMBType[mbX], p0,
                        predModeTop[blk8x8X + 1], p1, mbX, 2, 0, 2, 4, list);
                int mvdY2 = readMVD(reader, 1, true, topAvailable, MBType.P_8x16, topMBType[mbX], p0,
                        predModeTop[blk8x8X + 1], p1, mbX, 2, 0, 2, 4, list);

                int mvpX2 = calcMVPrediction8x16Right(v1, mvTop[list][(mbX << 2) + 2], mvTop[list][(mbX << 2) + 4],
                        mvTop[list][(mbX << 2) + 1], true, topAvailable, trAvailable, topAvailable, refIdx2[list], 0);
                int mvpY2 = calcMVPrediction8x16Right(v1, mvTop[list][(mbX << 2) + 2], mvTop[list][(mbX << 2) + 4],
                        mvTop[list][(mbX << 2) + 1], true, topAvailable, trAvailable, topAvailable, refIdx2[list], 1);

                mvX2 = mvdX2 + mvpX2;
                mvY2 = mvdY2 + mvpY2;

                debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mvdX2 + ", " + mvdY2 + "), MV: (" + mvX2 + ","
                        + mvY2 + "," + refIdx2[list] + ")");

                BlockInterpolator.getBlockLuma(references[list][refIdx2[list]], mb, 8, (mbX << 6) + 32 + mvX2, (mbY << 6)
                        + mvY2, 8, 16);
                r2 = refIdx2[list];
            }
            int[] v2 = { mvX2, mvY2, r2 };

            copyVect(mvTopLeft[list], mvTop[list][xx + 3]);
            saveVect(mvTop[list], xx, xx + 2, mvX1, mvY1, r1);
            saveVect(mvTop[list], xx + 2, xx + 4, mvX2, mvY2, r2);
            saveVect(mvLeft[list], 0, 4, mvX2, mvY2, r2);

            x[list] = new int[][] { v1, v1, v2, v2, v1, v1, v2, v2, v1, v1, v2, v2, v1, v1, v2, v2 };
        }

        private void decodeInter16x16(BitReader reader, Picture mb, Frame[][] refs, int mbIdx, MBType prevMbType,
                nVideo.Codecs.H264.H264Const.PartPred p0, MBType curMBType)
        {
            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            bool leftAvailable = mapper.leftAvailable(mbIdx);
            bool topAvailable = mapper.topAvailable(mbIdx);
            bool topLeftAvailable = mapper.topLeftAvailable(mbIdx);
            bool topRightAvailable = mapper.topRightAvailable(mbIdx);
            int address = mapper.getAddress(mbIdx);
            int[][][] x = new int[2][][];

            int xx = mbX << 2;
            int[] refIdx = { 0, 0 };
            for (int list = 0; list < 2; list++)
            {
                if (p0.usesList(list) && numRef[list] > 1)
                    refIdx[list] = readRefIdx(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            predModeLeft[0], predModeTop[(mbX << 1)], p0, mbX, 0, 0, 4, 4, list);
            }
            Picture[] mbb = { Picture.Create(16, 16, chromaFormat), Picture.Create(16, 16, chromaFormat) };
            for (int list = 0; list < 2; list++)
            {
                predictInter16x16(reader, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable,
                        topRightAvailable, x, xx, refIdx, list, p0);
            }

            prediction.mergePrediction(x[0][0][2], x[1][0][2], p0, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), 0,
                    16, 16, 16, mb.getPlaneData(0), refs, thisFrame);

            predModeLeft[0] = predModeLeft[1] = predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = p0;

            residualInter(reader, mb, refs, leftAvailable, topAvailable, mbX, mbY, x, new nVideo.Codecs.H264.H264Const.PartPred[] { p0, p0, p0, p0 },
                    mapper.getAddress(mbIdx), prevMbType, curMBType);

            collectPredictors(mb, mbX);

            mbTypes[address] = topMBType[mbX] = leftMBType = curMBType;
        }

        private void predictInter16x16(BitReader reader, Picture mb, Picture[][] references, int mbX, int mbY,
                bool leftAvailable, bool topAvailable, bool tlAvailable, bool trAvailable, int[][][] x, int xx,
                int[] refIdx, int list, nVideo.Codecs.H264.H264Const.PartPred curPred)
        {
            int blk8x8X = (mbX << 1);

            int mvX = 0, mvY = 0, r = -1;
            if (curPred.usesList(list))
            {
                int mvpX = calcMVPredictionMedian(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 4],
                        mvTopLeft[list], leftAvailable, topAvailable, trAvailable, tlAvailable, refIdx[list], 0);
                int mvpY = calcMVPredictionMedian(mvLeft[list][0], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 4],
                        mvTopLeft[list], leftAvailable, topAvailable, trAvailable, tlAvailable, refIdx[list], 1);
                int mvdX = readMVD(reader, 0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], curPred, mbX, 0, 0, 4, 4, list);
                int mvdY = readMVD(reader, 1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], predModeLeft[0],
                        predModeTop[blk8x8X], curPred, mbX, 0, 0, 4, 4, list);

                mvX = mvdX + mvpX;
                mvY = mvdY + mvpY;

                debugPrint("MVP: (" + mvpX + ", " + mvpY + "), MVD: (" + mvdX + ", " + mvdY + "), MV: (" + mvX + "," + mvY
                        + "," + refIdx[list] + ")");
                r = refIdx[list];

                BlockInterpolator.getBlockLuma(references[list][r], mb, 0, (mbX << 6) + mvX, (mbY << 6) + mvY, 16, 16);
            }

            copyVect(mvTopLeft[list], mvTop[list][xx + 3]);
            saveVect(mvTop[list], xx, xx + 4, mvX, mvY, r);
            saveVect(mvLeft[list], 0, 4, mvX, mvY, r);

            int[] v = { mvX, mvY, r };
            x[list] = new int[][] { v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v };
        }

        private int readRefIdx(BitReader reader, bool leftAvailable, bool topAvailable, MBType leftType,
                MBType topType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred, nVideo.Codecs.H264.H264Const.PartPred curPred, int mbX, int partX, int partY,
                int partW, int partH, int list)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readTE(reader, numRef[list] - 1);
            else
                return cabac.readRefIdx(mDecoder, leftAvailable, topAvailable, leftType, topType, leftPred, topPred,
                        curPred, mbX, partX, partY, partW, partH, list);
        }

        private void saveVect(int[][] mv, int from, int to, int x, int y, int r)
        {
            for (int i = from; i < to; i++)
            {
                mv[i][0] = x;
                mv[i][1] = y;
                mv[i][2] = r;
            }
        }

        public int calcMVPredictionMedian(int[] a, int[] b, int[] c, int[] d, bool aAvb, bool bAvb, bool cAvb,
                bool dAvb, int refs, int comp)
        {

            if (!cAvb)
            {
                c = d;
                cAvb = dAvb;
            }

            if (aAvb && !bAvb && !cAvb)
            {
                b = c = a;
                bAvb = cAvb = aAvb;
            }

            a = aAvb ? a : NULL_VECTOR;
            b = bAvb ? b : NULL_VECTOR;
            c = cAvb ? c : NULL_VECTOR;

            if (a[2] == refs && b[2] != refs && c[2] != refs)
                return a[comp];
            else if (b[2] == refs && a[2] != refs && c[2] != refs)
                return b[comp];
            else if (c[2] == refs && a[2] != refs && b[2] != refs)
                return c[comp];

            return a[comp] + b[comp] + c[comp] - min(a[comp], b[comp], c[comp]) - max(a[comp], b[comp], c[comp]);
        }

        private int max(int x, int x2, int x3)
        {
            return x > x2 ? (x > x3 ? x : x3) : (x2 > x3 ? x2 : x3);
        }

        private int min(int x, int x2, int x3)
        {
            return x < x2 ? (x < x3 ? x : x3) : (x2 < x3 ? x2 : x3);
        }

        public int calcMVPrediction16x8Top(int[] a, int[] b, int[] c, int[] d, bool aAvb, bool bAvb, bool cAvb,
                bool dAvb, int refIdx, int comp)
        {
            if (bAvb && b[2] == refIdx)
                return b[comp];
            else
                return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp);
        }

        public int calcMVPrediction16x8Bottom(int[] a, int[] b, int[] c, int[] d, bool aAvb, bool bAvb, bool cAvb,
                bool dAvb, int refIdx, int comp)
        {

            if (aAvb && a[2] == refIdx)
                return a[comp];
            else
                return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp);
        }

        public int calcMVPrediction8x16Left(int[] a, int[] b, int[] c, int[] d, bool aAvb, bool bAvb, bool cAvb,
                bool dAvb, int refIdx, int comp)
        {

            if (aAvb && a[2] == refIdx)
                return a[comp];
            else
                return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp);
        }

        public int calcMVPrediction8x16Right(int[] a, int[] b, int[] c, int[] d, bool aAvb, bool bAvb, bool cAvb,
                bool dAvb, int refIdx, int comp)
        {
            int[] lc = cAvb ? c : (dAvb ? d : NULL_VECTOR);

            if (lc[2] == refIdx)
                return lc[comp];
            else
                return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp);
        }

        public void decodeMBInter8x8(BitReader reader, int mb_type, Frame[][] references, Picture mb, SliceType sliceType,
                int mbIdx, bool mb_field_decoding_flag, MBType prevMbType, bool ref0)
        {

            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            int mbAddr = mapper.getAddress(mbIdx);
            bool leftAvailable = mapper.leftAvailable(mbIdx);
            bool topAvailable = mapper.topAvailable(mbIdx);
            bool topLeftAvailable = mapper.topLeftAvailable(mbIdx);
            bool topRightAvailable = mapper.topRightAvailable(mbIdx);

            int[][][] x = (int[][][])Array.CreateInstance(typeof(int), new int[] { 2, 16, 3 });//new int[2][16][3];
            nVideo.Codecs.H264.H264Const.PartPred[] pp = new nVideo.Codecs.H264.H264Const.PartPred[4];
            for (int i = 0; i < 16; i++)
                x[0][i][2] = x[1][i][2] = -1;

            Picture mb1 = Picture.Create(16, 16, chromaFormat);

            MBType curMBType;
            bool noSubMBLessThen8x8;
            if (sliceType == SliceType.P)
            {
                noSubMBLessThen8x8 = predict8x8P(reader, references[0], mb1, ref0, mbX, mbY, leftAvailable, topAvailable,
                        topLeftAvailable, topRightAvailable, x, pp);
                curMBType = MBType.P_8x8;
            }
            else
            {
                noSubMBLessThen8x8 = predict8x8B(reader, references, mb1, ref0, mbX, mbY, leftAvailable, topAvailable,
                        topLeftAvailable, topRightAvailable, x, pp);
                curMBType = MBType.B_8x8;
            }

            int codedBlockPattern = readCodedBlockPatternInter(reader, leftAvailable, topAvailable, leftCBPLuma
                    | (leftCBPChroma << 4), topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]);

            int cbpLuma = codedBlockPattern & 0xf;
            int cbpChroma = codedBlockPattern >> 4;

            bool transform8x8Used = false;
            if (transform8x8 && cbpLuma != 0 && noSubMBLessThen8x8)
            {
                transform8x8Used = readTransform8x8Flag(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                        tf8x8Left, tf8x8Top[mbX]);
            }

            if (cbpLuma > 0 || cbpChroma > 0)
            {
                qp = (qp + readMBQpDelta(reader, prevMbType) + 52) % 52;
            }
            mbQps[0][mbAddr] = qp;

            residualLuma(reader, leftAvailable, topAvailable, mbX, mbY, mb, codedBlockPattern, curMBType, transform8x8Used,
                    tf8x8Left, tf8x8Top[mbX]);

            saveMvs(x, mbX, mbY);

            decodeChromaInter(reader, codedBlockPattern >> 4, references, x, pp, leftAvailable, topAvailable, mbX, mbY,
                    mbAddr, qp, mb, mb1);

            mergeResidual(mb, mb1);

            collectPredictors(mb, mbX);

            mbTypes[mbAddr] = topMBType[mbX] = leftMBType = curMBType;
            topCBPLuma[mbX] = leftCBPLuma = cbpLuma;
            topCBPChroma[mbX] = leftCBPChroma = cbpChroma;
            tf8x8Left = tf8x8Top[mbX] = transform8x8Used;
            tr8x8Used[mbAddr] = transform8x8Used;
        }

        private bool predict8x8P(BitReader reader, Picture[] references, Picture mb, bool ref0, int mbX, int mbY,
                bool leftAvailable, bool topAvailable, bool tlAvailable, bool topRightAvailable, int[][][] x,
                nVideo.Codecs.H264.H264Const.PartPred[] pp)
        {
            int[] subMbTypes = new int[4];
            for (int i = 0; i < 4; i++)
            {
                subMbTypes[i] = readSubMBTypeP(reader);
            }
            Arrays.fill(pp, nVideo.Codecs.H264.H264Const.PartPred.L0);
            int blk8x8X = mbX << 1;

            int[] refIdx = new int[4];
            if (numRef[0] > 1 && !ref0)
            {
                refIdx[0] = readRefIdx(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX], nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, mbX, 0,
                        0, 2, 2, 0);
                refIdx[1] = readRefIdx(reader, true, topAvailable, MBType.P_8x8, topMBType[mbX], nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, mbX, 2, 0, 2, 2, 0);
                refIdx[2] = readRefIdx(reader, leftAvailable, true, leftMBType, MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, mbX, 0, 2, 2, 2, 0);
                refIdx[3] = readRefIdx(reader, true, true, MBType.P_8x8, MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, mbX, 2, 2, 2, 2, 0);
            }

            decodeSubMb8x8(reader, subMbTypes[0], references, mbX << 6, mbY << 6, x[0], mvTopLeft[0], mvTop[0][mbX << 2],
                    mvTop[0][(mbX << 2) + 1], mvTop[0][(mbX << 2) + 2], mvLeft[0][0], mvLeft[0][1], tlAvailable,
                    topAvailable, topAvailable, leftAvailable, x[0][0], x[0][1], x[0][4], x[0][5], refIdx[0], mb, 0, 0, 0,
                    mbX, leftMBType, topMBType[mbX], MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, 0);

            decodeSubMb8x8(reader, subMbTypes[1], references, (mbX << 6) + 32, mbY << 6, x[0], mvTop[0][(mbX << 2) + 1],
                    mvTop[0][(mbX << 2) + 2], mvTop[0][(mbX << 2) + 3], mvTop[0][(mbX << 2) + 4], x[0][1], x[0][5],
                    topAvailable, topAvailable, topRightAvailable, true, x[0][2], x[0][3], x[0][6], x[0][7], refIdx[1], mb,
                    8, 2, 0, mbX, MBType.P_8x8, topMBType[mbX], MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, 0);

            decodeSubMb8x8(reader, subMbTypes[2], references, mbX << 6, (mbY << 6) + 32, x[0], mvLeft[0][1], x[0][4],
                    x[0][5], x[0][6], mvLeft[0][2], mvLeft[0][3], leftAvailable, true, true, leftAvailable, x[0][8],
                    x[0][9], x[0][12], x[0][13], refIdx[2], mb, 128, 0, 2, mbX, leftMBType, MBType.P_8x8, MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, 0);

            decodeSubMb8x8(reader, subMbTypes[3], references, (mbX << 6) + 32, (mbY << 6) + 32, x[0], x[0][5], x[0][6],
                    x[0][7], null, x[0][9], x[0][13], true, true, false, true, x[0][10], x[0][11], x[0][14], x[0][15],
                    refIdx[3], mb, 136, 2, 2, mbX, MBType.P_8x8, MBType.P_8x8, MBType.P_8x8, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, nVideo.Codecs.H264.H264Const.PartPred.L0, 0);

            savePrediction8x8(mbX, x[0], 0);

            predModeLeft[0] = predModeLeft[1] = predModeTop[blk8x8X] = predModeTop[blk8x8X + 1] = nVideo.Codecs.H264.H264Const.PartPred.L0;

            return subMbTypes[0] == 0 && subMbTypes[1] == 0 && subMbTypes[2] == 0 && subMbTypes[3] == 0;
        }

        private bool predict8x8B(BitReader reader, Frame[][] refs, Picture mb, bool ref0, int mbX, int mbY,
                bool leftAvailable, bool topAvailable, bool tlAvailable, bool topRightAvailable, int[][][] x,
                nVideo.Codecs.H264.H264Const.PartPred[] p)
        {

            int[] subMbTypes = new int[4];
            for (int i = 0; i < 4; i++)
            {
                subMbTypes[i] = readSubMBTypeB(reader);
                p[i] = (nVideo.Codecs.H264.H264Const.PartPred)H264Const.bPartPredModes[subMbTypes[i]];
            }

            int[][] refIdx = (int[][])Array.CreateInstance(typeof(int), 2, 4);
            for (int list = 0; list < 2; list++)
            {
                if (numRef[list] <= 1)
                    continue;
                if (p[0].usesList(list))
                    refIdx[list][0] = readRefIdx(reader, leftAvailable, topAvailable, leftMBType, topMBType[mbX],
                            predModeLeft[0], predModeTop[mbX << 1], p[0], mbX, 0, 0, 2, 2, list);
                if (p[1].usesList(list))
                    refIdx[list][1] = readRefIdx(reader, true, topAvailable, MBType.B_8x8, topMBType[mbX], p[0],
                            predModeTop[(mbX << 1) + 1], p[1], mbX, 2, 0, 2, 2, list);
                if (p[2].usesList(list))
                    refIdx[list][2] = readRefIdx(reader, leftAvailable, true, leftMBType, MBType.B_8x8, predModeLeft[1], p[0],
                            p[2], mbX, 0, 2, 2, 2, list);
                if (p[3].usesList(list))
                    refIdx[list][3] = readRefIdx(reader, true, true, MBType.B_8x8, MBType.B_8x8, p[2], p[1], p[3], mbX, 2, 2, 2, 2, list);
            }

            Picture[] mbb = { Picture.Create(16, 16, chromaFormat), Picture.Create(16, 16, chromaFormat) };

            nVideo.Codecs.H264.H264Const.PartPred[] _pp = new nVideo.Codecs.H264.H264Const.PartPred[4];
            for (int i = 0; i < 4; i++)
            {
                if (p[i] == nVideo.Codecs.H264.H264Const.PartPred.Direct)
                    predictBDirect(refs, mbX, mbY, leftAvailable, topAvailable, tlAvailable, topRightAvailable, x, _pp, mb,
                            H264Const.ARRAY[i]);
            }

            int blk8x8X = mbX << 1;
            for (int list = 0; list < 2; list++)
            {
                if (p[0].usesList(list))
                    decodeSubMb8x8(reader, H264Const.bSubMbTypes[subMbTypes[0]], refs[list], mbX << 6, mbY << 6, x[list],
                            mvTopLeft[list], mvTop[list][mbX << 2], mvTop[list][(mbX << 2) + 1],
                            mvTop[list][(mbX << 2) + 2], mvLeft[list][0], mvLeft[list][1], tlAvailable, topAvailable,
                            topAvailable, leftAvailable, x[list][0], x[list][1], x[list][4], x[list][5], refIdx[list][0],
                            mbb[list], 0, 0, 0, mbX, leftMBType, topMBType[mbX], MBType.B_8x8, predModeLeft[0],
                            predModeTop[blk8x8X], p[0], list);

                if (p[1].usesList(list))
                    decodeSubMb8x8(reader, H264Const.bSubMbTypes[subMbTypes[1]], refs[list], (mbX << 6) + 32, mbY << 6, x[list],
                            mvTop[list][(mbX << 2) + 1], mvTop[list][(mbX << 2) + 2], mvTop[list][(mbX << 2) + 3],
                            mvTop[list][(mbX << 2) + 4], x[list][1], x[list][5], topAvailable, topAvailable,
                            topRightAvailable, true, x[list][2], x[list][3], x[list][6], x[list][7], refIdx[list][1],
                            mbb[list], 8, 2, 0, mbX, MBType.B_8x8, topMBType[mbX], MBType.B_8x8, p[0], predModeTop[blk8x8X + 1], p[1],
                            list);

                if (p[2].usesList(list))
                    decodeSubMb8x8(reader, H264Const.bSubMbTypes[subMbTypes[2]], refs[list], mbX << 6, (mbY << 6) + 32, x[list],
                            mvLeft[list][1], x[list][4], x[list][5], x[list][6], mvLeft[list][2], mvLeft[list][3],
                            leftAvailable, true, true, leftAvailable, x[list][8], x[list][9], x[list][12], x[list][13],
                            refIdx[list][2], mbb[list], 128, 0, 2, mbX, leftMBType, MBType.B_8x8, MBType.B_8x8, predModeLeft[1], p[0],
                            p[2], list);

                if (p[3].usesList(list))
                    decodeSubMb8x8(reader, H264Const.bSubMbTypes[subMbTypes[3]], refs[list], (mbX << 6) + 32, (mbY << 6) + 32,
                            x[list], x[list][5], x[list][6], x[list][7], null, x[list][9], x[list][13], true, true, false,
                            true, x[list][10], x[list][11], x[list][14], x[list][15], refIdx[list][3], mbb[list], 136, 2,
                            2, mbX, MBType.B_8x8, MBType.B_8x8, MBType.B_8x8, p[2], p[1], p[3], list);
            }

            for (int i = 0; i < 4; i++)
            {
                int blk4x4 = H264Const.BLK8x8_BLOCKS[i][0];
                prediction.mergePrediction(x[0][blk4x4][2], x[1][blk4x4][2], p[i], 0, mbb[0].getPlaneData(0),
                        mbb[1].getPlaneData(0), H264Const.BLK_8x8_MB_OFF_LUMA[i], 16, 8, 8, mb.getPlaneData(0), refs, thisFrame);
            }

            predModeLeft[0] = p[1];
            predModeTop[blk8x8X] = p[2];
            predModeLeft[1] = predModeTop[blk8x8X + 1] = p[3];

            savePrediction8x8(mbX, x[0], 0);
            savePrediction8x8(mbX, x[1], 1);

            for (int i = 0; i < 4; i++)
                if (p[i] == nVideo.Codecs.H264.H264Const.PartPred.Direct)
                    p[i] = _pp[i];

            return H264Const.bSubMbTypes[subMbTypes[0]] == 0 && H264Const.bSubMbTypes[subMbTypes[1]] == 0 && H264Const.bSubMbTypes[subMbTypes[2]] == 0
                    && H264Const.bSubMbTypes[subMbTypes[3]] == 0;
        }

        private void decodeMBBiDirect(int mbIdx, BitReader reader, bool field, MBType prevMbType, Picture mb,
                Frame[][] references)
        {
            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            int mbAddr = mapper.getAddress(mbIdx);
            bool lAvb = mapper.leftAvailable(mbIdx);
            bool tAvb = mapper.topAvailable(mbIdx);
            bool tlAvb = mapper.topLeftAvailable(mbIdx);
            bool trAvb = mapper.topRightAvailable(mbIdx);

            int[][][] x = (int[][][])Array.CreateInstance(typeof(int), new int[] { 2, 16, 3 });
            for (int i = 0; i < 16; i++)
                x[0][i][2] = x[1][i][2] = -1;

            Picture mb1 = Picture.Create(16, 16, chromaFormat);
            nVideo.Codecs.H264.H264Const.PartPred[] pp = new nVideo.Codecs.H264.H264Const.PartPred[4];

            predictBDirect(references, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, x, pp, mb1, H264Const.identityMapping4);

            int codedBlockPattern = readCodedBlockPatternInter(reader, lAvb, tAvb, leftCBPLuma | (leftCBPChroma << 4),
                    topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]);

            int cbpLuma = codedBlockPattern & 0xf;
            int cbpChroma = codedBlockPattern >> 4;

            bool transform8x8Used = false;
            if (transform8x8 && cbpLuma != 0 && activeSps.direct_8x8_inference_flag)
            {
                transform8x8Used = readTransform8x8Flag(reader, lAvb, tAvb, leftMBType, topMBType[mbX], tf8x8Left,
                        tf8x8Top[mbX]);
            }

            if (cbpLuma > 0 || cbpChroma > 0)
            {
                qp = (qp + readMBQpDelta(reader, prevMbType) + 52) % 52;
            }
            mbQps[0][mbAddr] = qp;

            residualLuma(reader, lAvb, tAvb, mbX, mbY, mb, codedBlockPattern, MBType.P_8x8, transform8x8Used, tf8x8Left,
                    tf8x8Top[mbX]);

            savePrediction8x8(mbX, x[0], 0);
            savePrediction8x8(mbX, x[1], 1);
            saveMvs(x, mbX, mbY);

            decodeChromaInter(reader, codedBlockPattern >> 4, references, x, pp, lAvb, tAvb, mbX, mbY, mbAddr, qp, mb, mb1);

            mergeResidual(mb, mb1);

            collectPredictors(mb, mbX);

            mbTypes[mbAddr] = topMBType[mbX] = leftMBType = MBType.B_Direct_16x16;
            topCBPLuma[mbX] = leftCBPLuma = cbpLuma;
            topCBPChroma[mbX] = leftCBPChroma = cbpChroma;
            tf8x8Left = tf8x8Top[mbX] = transform8x8Used;
            tr8x8Used[mbAddr] = transform8x8Used;
            predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = predModeLeft[0] = predModeLeft[1] = nVideo.Codecs.H264.H264Const.PartPred.Direct;
        }

        private int readSubMBTypeP(BitReader reader)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readUE(reader, "SUB: sub_mb_type");
            else
                return cabac.readSubMbTypeP(mDecoder);
        }

        private int readSubMBTypeB(BitReader reader)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readUE(reader, "SUB: sub_mb_type");
            else
                return cabac.readSubMbTypeB(mDecoder);
        }

        private void savePrediction8x8(int mbX, int[][] x, int list)
        {
            copyVect(mvTopLeft[list], mvTop[list][(mbX << 2) + 3]);
            copyVect(mvLeft[list][0], x[3]);
            copyVect(mvLeft[list][1], x[7]);
            copyVect(mvLeft[list][2], x[11]);
            copyVect(mvLeft[list][3], x[15]);
            copyVect(mvTop[list][mbX << 2], x[12]);
            copyVect(mvTop[list][(mbX << 2) + 1], x[13]);
            copyVect(mvTop[list][(mbX << 2) + 2], x[14]);
            copyVect(mvTop[list][(mbX << 2) + 3], x[15]);
        }

        private void copyVect(int[] to, int[] from)
        {
            to[0] = from[0];
            to[1] = from[1];
            to[2] = from[2];
        }

        private void decodeSubMb8x8(BitReader reader, int subMbType, Picture[] references, int offX, int offY, int[][] x,
                int[] tl, int[] t0, int[] t1, int[] tr, int[] l0, int[] l1, bool tlAvb, bool tAvb, bool trAvb,
                bool lAvb, int[] x00, int[] x01, int[] x10, int[] x11, int refIdx, Picture mb, int off, int blk8x8X,
                int blk8x8Y, int mbX, MBType leftMBType, MBType topMBType, MBType curMBType, nVideo.Codecs.H264.H264Const.PartPred leftPred,
                nVideo.Codecs.H264.H264Const.PartPred topPred, nVideo.Codecs.H264.H264Const.PartPred partPred, int list)
        {

            x00[2] = x01[2] = x10[2] = x11[2] = refIdx;

            switch (subMbType)
            {
                case 3:
                    decodeSub4x4(reader, references, offX, offY, tl, t0, t1, tr, l0, l1, tlAvb, tAvb, trAvb, lAvb, x00, x01,
                            x10, x11, refIdx, mb, off, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred,
                            topPred, partPred, list);
                    break;
                case 2:
                    decodeSub4x8(reader, references, offX, offY, tl, t0, t1, tr, l0, tlAvb, tAvb, trAvb, lAvb, x00, x01, x10,
                            x11, refIdx, mb, off, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, topPred,
                            partPred, list);
                    break;
                case 1:
                    decodeSub8x4(reader, references, offX, offY, tl, t0, tr, l0, l1, tlAvb, tAvb, trAvb, lAvb, x00, x01, x10,
                            x11, refIdx, mb, off, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, topPred,
                            partPred, list);
                    break;
                case 0:
                    decodeSub8x8(reader, references, offX, offY, tl, t0, tr, l0, tlAvb, tAvb, trAvb, lAvb, x00, x01, x10, x11,
                            refIdx, mb, off, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, topPred,
                            partPred, list);
                    break;
            }
        }

        private void decodeSub8x8(BitReader reader, Picture[] references, int offX, int offY, int[] tl, int[] t0, int[] tr,
                int[] l0, bool tlAvb, bool tAvb, bool trAvb, bool lAvb, int[] x00, int[] x01, int[] x10,
                int[] x11, int refIdx, Picture mb, int off, int blk8x8X, int blk8x8Y, int mbX, MBType leftMBType,
                MBType topMBType, MBType curMBType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred, nVideo.Codecs.H264.H264Const.PartPred partPred, int list)
        {

            int mvdX = readMVD(reader, 0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 2, 2, list);
            int mvdY = readMVD(reader, 1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 2, 2, list);

            int mvpX = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 0);
            int mvpY = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 1);

            x00[0] = x01[0] = x10[0] = x11[0] = mvdX + mvpX;
            x00[1] = x01[1] = x10[1] = x11[1] = mvpY + mvdY;

            debugPrint("MVP: (" + mvpX + ", " + mvpY + "), MVD: (" + mvdX + ", " + mvdY + "), MV: (" + x00[0] + ","
                    + x00[1] + "," + refIdx + ")");
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off, offX + x00[0], offY + x00[1], 8, 8);
        }

        private void decodeSub8x4(BitReader reader, Picture[] references, int offX, int offY, int[] tl, int[] t0, int[] tr,
                int[] l0, int[] l1, bool tlAvb, bool tAvb, bool trAvb, bool lAvb, int[] x00, int[] x01,
                int[] x10, int[] x11, int refIdx, Picture mb, int off, int blk8x8X, int blk8x8Y, int mbX,
                MBType leftMBType, MBType topMBType, MBType curMBType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred,
                nVideo.Codecs.H264.H264Const.PartPred partPred, int list)
        {

            int mvdX1 = readMVD(reader, 0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 2, 1, list);
            int mvdY1 = readMVD(reader, 1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 2, 1, list);

            int mvpX1 = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 0);
            int mvpY1 = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 1);

            x00[0] = x01[0] = mvdX1 + mvpX1;
            x00[1] = x01[1] = mvdY1 + mvpY1;

            debugPrint("MVP: (" + mvpX1 + ", " + mvpY1 + "), MVD: (" + mvdX1 + ", " + mvdY1 + "), MV: (" + x00[0] + ","
                    + x00[1] + "," + refIdx + ")");

            int mvdX2 = readMVD(reader, 0, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, mbX, blk8x8X,
                    blk8x8Y + 1, 2, 1, list);
            int mvdY2 = readMVD(reader, 1, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, mbX, blk8x8X,
                    blk8x8Y + 1, 2, 1, list);

            int mvpX2 = calcMVPredictionMedian(l1, x00, NULL_VECTOR, l0, lAvb, true, false, lAvb, refIdx, 0);
            int mvpY2 = calcMVPredictionMedian(l1, x00, NULL_VECTOR, l0, lAvb, true, false, lAvb, refIdx, 1);

            x10[0] = x11[0] = mvdX2 + mvpX2;
            x10[1] = x11[1] = mvdY2 + mvpY2;

            debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mvdX2 + ", " + mvdY2 + "), MV: (" + x10[0] + ","
                    + x10[1] + "," + refIdx + ")");

            BlockInterpolator.getBlockLuma(references[refIdx], mb, off, offX + x00[0], offY + x00[1], 8, 4);
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4, offX + x10[0], offY + x10[1]
                    + 16, 8, 4);
        }

        private void decodeSub4x8(BitReader reader, Picture[] references, int offX, int offY, int[] tl, int[] t0, int[] t1,
                int[] tr, int[] l0, bool tlAvb, bool tAvb, bool trAvb, bool lAvb, int[] x00, int[] x01,
                int[] x10, int[] x11, int refIdx, Picture mb, int off, int blk8x8X, int blk8x8Y, int mbX,
                MBType leftMBType, MBType topMBType, MBType curMBType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred,
                nVideo.Codecs.H264.H264Const.PartPred partPred, int list)
        {

            int mvdX1 = readMVD(reader, 0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 1, 2, list);
            int mvdY1 = readMVD(reader, 1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 1, 2, list);

            int mvpX1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 0);
            int mvpY1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 1);

            x00[0] = x10[0] = mvdX1 + mvpX1;
            x00[1] = x10[1] = mvdY1 + mvpY1;

            debugPrint("MVP: (" + mvpX1 + ", " + mvpY1 + "), MVD: (" + mvdX1 + ", " + mvdY1 + "), MV: (" + x00[0] + ","
                    + x00[1] + "," + refIdx + ")");

            int mvdX2 = readMVD(reader, 0, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, mbX, blk8x8X + 1,
                    blk8x8Y, 1, 2, list);
            int mvdY2 = readMVD(reader, 1, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, mbX, blk8x8X + 1,
                    blk8x8Y, 1, 2, list);

            int mvpX2 = calcMVPredictionMedian(x00, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 0);
            int mvpY2 = calcMVPredictionMedian(x00, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 1);

            x01[0] = x11[0] = mvdX2 + mvpX2;
            x01[1] = x11[1] = mvdY2 + mvpY2;

            debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mvdX2 + ", " + mvdY2 + "), MV: (" + x01[0] + ","
                    + x01[1] + "," + refIdx + ")");

            BlockInterpolator.getBlockLuma(references[refIdx], mb, off, offX + x00[0], offY + x00[1], 4, 8);
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off + 4, offX + x01[0] + 16, offY + x01[1], 4, 8);
        }

        private void decodeSub4x4(BitReader reader, Picture[] references, int offX, int offY, int[] tl, int[] t0, int[] t1,
                int[] tr, int[] l0, int[] l1, bool tlAvb, bool tAvb, bool trAvb, bool lAvb, int[] x00,
                int[] x01, int[] x10, int[] x11, int refIdx, Picture mb, int off, int blk8x8X, int blk8x8Y, int mbX,
                MBType leftMBType, MBType topMBType, MBType curMBType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred,
                nVideo.Codecs.H264.H264Const.PartPred partPred, int list)
        {
            int mvdX1 = readMVD(reader, 0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 1, 1, list);
            int mvdY1 = readMVD(reader, 1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, mbX, blk8x8X,
                    blk8x8Y, 1, 1, list);

            int mvpX1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 0);
            int mvpY1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 1);

            x00[0] = mvdX1 + mvpX1;
            x00[1] = mvdY1 + mvpY1;
            debugPrint("MVP: (" + mvpX1 + ", " + mvpY1 + "), MVD: (" + mvdX1 + ", " + mvdY1 + "), MV: (" + x00[0] + ","
                    + x00[1] + "," + refIdx + ")");

            int mvdX2 = readMVD(reader, 0, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, mbX, blk8x8X + 1,
                    blk8x8Y, 1, 1, list);
            int mvdY2 = readMVD(reader, 1, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, mbX, blk8x8X + 1,
                    blk8x8Y, 1, 1, list);

            int mvpX2 = calcMVPredictionMedian(x00, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 0);
            int mvpY2 = calcMVPredictionMedian(x00, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 1);

            x01[0] = mvdX2 + mvpX2;
            x01[1] = mvdY2 + mvpY2;
            debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mvdX2 + ", " + mvdY2 + "), MV: (" + x01[0] + ","
                    + x01[1] + "," + refIdx + ")");

            int mvdX3 = readMVD(reader, 0, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, mbX, blk8x8X,
                    blk8x8Y + 1, 1, 1, list);
            int mvdY3 = readMVD(reader, 1, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, mbX, blk8x8X,
                    blk8x8Y + 1, 1, 1, list);

            int mvpX3 = calcMVPredictionMedian(l1, x00, x01, l0, lAvb, true, true, lAvb, refIdx, 0);
            int mvpY3 = calcMVPredictionMedian(l1, x00, x01, l0, lAvb, true, true, lAvb, refIdx, 1);

            x10[0] = mvdX3 + mvpX3;
            x10[1] = mvdY3 + mvpY3;

            debugPrint("MVP: (" + mvpX3 + ", " + mvpY3 + "), MVD: (" + mvdX3 + ", " + mvdY3 + "), MV: (" + x10[0] + ","
                    + x10[1] + "," + refIdx + ")");

            int mvdX4 = readMVD(reader, 0, true, true, curMBType, curMBType, partPred, partPred, partPred, mbX,
                    blk8x8X + 1, blk8x8Y + 1, 1, 1, list);
            int mvdY4 = readMVD(reader, 1, true, true, curMBType, curMBType, partPred, partPred, partPred, mbX,
                    blk8x8X + 1, blk8x8Y + 1, 1, 1, list);

            int mvpX4 = calcMVPredictionMedian(x10, x01, NULL_VECTOR, x00, true, true, false, true, refIdx, 0);
            int mvpY4 = calcMVPredictionMedian(x10, x01, NULL_VECTOR, x00, true, true, false, true, refIdx, 1);

            x11[0] = mvdX4 + mvpX4;
            x11[1] = mvdY4 + mvpY4;

            debugPrint("MVP: (" + mvpX4 + ", " + mvpY4 + "), MVD: (" + mvdX4 + ", " + mvdY4 + "), MV: (" + x11[0] + ","
                    + x11[1] + "," + refIdx + ")");

            BlockInterpolator.getBlockLuma(references[refIdx], mb, off, offX + x00[0], offY + x00[1], 4, 4);
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off + 4, offX + x01[0] + 16, offY + x01[1], 4, 4);
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4, offX + x10[0], offY + x10[1]
                    + 16, 4, 4);
            BlockInterpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4 + 4, offX + x11[0] + 16, offY
                    + x11[1] + 16, 4, 4);
        }

        private int readMVD(BitReader reader, int comp, bool leftAvailable, bool topAvailable, MBType leftType,
                MBType topType, nVideo.Codecs.H264.H264Const.PartPred leftPred, nVideo.Codecs.H264.H264Const.PartPred topPred, nVideo.Codecs.H264.H264Const.PartPred curPred, int mbX, int partX, int partY,
                int partW, int partH, int list)
        {
            if (!activePps.entropy_coding_mode_flag)
                return CAVLCReader.readSE(reader, "mvd_l0_x");
            else
                return cabac.readMVD(mDecoder, comp, leftAvailable, topAvailable, leftType, topType, leftPred, topPred,
                        curPred, mbX, partX, partY, partW, partH, list);
        }

        public void decodeChromaInter(BitReader reader, int pattern, Frame[][] refs, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] predType,
                bool leftAvailable, bool topAvailable, int mbX, int mbY, int mbAddr, int qp, Picture mb, Picture mb1)
        {

            predictChromaInter(refs, x, mbX << 3, mbY << 3, 1, mb1, predType);
            predictChromaInter(refs, x, mbX << 3, mbY << 3, 2, mb1, predType);

            int qp1 = calcQpChroma(qp, chromaQpOffset[0]);
            int qp2 = calcQpChroma(qp, chromaQpOffset[1]);

            decodeChromaResidual(reader, leftAvailable, topAvailable, mbX, mbY, pattern, mb, qp1, qp2, MBType.P_16x16);

            mbQps[1][mbAddr] = qp1;
            mbQps[2][mbAddr] = qp2;
            // throw new RuntimeException("Merge prediction and residual");
        }

        private void saveMvs(int[][][] x, int mbX, int mbY)
        {
            for (int j = 0, blkOffY = mbY << 2, blkInd = 0; j < 4; j++, blkOffY++)
            {
                for (int i = 0, blkOffX = mbX << 2; i < 4; i++, blkOffX++, blkInd++)
                {
                    mvs[0][blkOffY][blkOffX] = x[0][blkInd];
                    mvs[1][blkOffY][blkOffX] = x[1][blkInd];
                }
            }
        }

        public void predictChromaInter(Frame[][] refs, int[][][] vectors, int x, int y, int comp, Picture mb,
                nVideo.Codecs.H264.H264Const.PartPred[] predType)
        {

            Picture[] mbb = { Picture.Create(16, 16, chromaFormat), Picture.Create(16, 16, chromaFormat) };

            for (int blk8x8 = 0; blk8x8 < 4; blk8x8++)
            {
                for (int list = 0; list < 2; list++)
                {
                    if (!predType[blk8x8].usesList(list))
                        continue;
                    for (int blk4x4 = 0; blk4x4 < 4; blk4x4++)
                    {
                        int i = H264Const.BLK_INV_MAP[(blk8x8 << 2) + blk4x4];
                        int[] mv = vectors[list][i];
                        Picture refp = refs[list][mv[2]];

                        int blkPox = (i & 3) << 1;
                        int blkPoy = (i >> 2) << 1;

                        int xx = ((x + blkPox) << 3) + mv[0];
                        int yy = ((y + blkPoy) << 3) + mv[1];

                        BlockInterpolator.getBlockChroma(refp.getPlaneData(comp), refp.getPlaneWidth(comp),
                                refp.getPlaneHeight(comp), mbb[list].getPlaneData(comp), blkPoy * mb.getPlaneWidth(comp)
                                        + blkPox, mb.getPlaneWidth(comp), xx, yy, 2, 2);
                    }
                }

                int blk4x4a = H264Const.BLK8x8_BLOCKS[blk8x8][0];
                prediction.mergePrediction(vectors[0][blk4x4a][2], vectors[1][blk4x4a][2], predType[blk8x8], comp,
                        mbb[0].getPlaneData(comp), mbb[1].getPlaneData(comp), H264Const.BLK_8x8_MB_OFF_CHROMA[blk8x8],
                        mb.getPlaneWidth(comp), 4, 4, mb.getPlaneData(comp), refs, thisFrame);
            }
        }

        public void decodeMBlockIPCM(BitReader reader, int mbIndex, Picture mb)
        {
            int mbX = mapper.getMbX(mbIndex);

            reader.align();

            int[] samplesLuma = new int[256];
            for (int i = 0; i < 256; i++)
            {
                samplesLuma[i] = reader.readNBit(8);
            }
            int MbWidthC = 16 >> chromaFormat.compWidth[1];
            int MbHeightC = 16 >> chromaFormat.compHeight[1];

            int[] samplesChroma = new int[2 * MbWidthC * MbHeightC];
            for (int i = 0; i < 2 * MbWidthC * MbHeightC; i++)
            {
                samplesChroma[i] = reader.readNBit(8);
            }
            collectPredictors(mb, mbX);
        }

        public void decodeSkip(Frame[][] refs, int mbIdx, Picture mb, SliceType sliceType)
        {
            int mbX = mapper.getMbX(mbIdx);
            int mbY = mapper.getMbY(mbIdx);
            int mbAddr = mapper.getAddress(mbIdx);

            int[][][] x = (int[][][])Array.CreateInstance(typeof(int), new int[] { 2, 16, 3 });
            nVideo.Codecs.H264.H264Const.PartPred[] pp = new nVideo.Codecs.H264.H264Const.PartPred[4];

            for (int i = 0; i < 16; i++)
                x[0][i][2] = x[1][i][2] = -1;

            if (sliceType == SliceType.P)
            {
                predictPSkip(refs, mbX, mbY, mapper.leftAvailable(mbIdx), mapper.topAvailable(mbIdx),
                        mapper.topLeftAvailable(mbIdx), mapper.topRightAvailable(mbIdx), x, mb);
                Arrays.fill(pp, nVideo.Codecs.H264.H264Const.PartPred.L0);
            }
            else
            {
                predictBDirect(refs, mbX, mbY, mapper.leftAvailable(mbIdx), mapper.topAvailable(mbIdx),
                        mapper.topLeftAvailable(mbIdx), mapper.topRightAvailable(mbIdx), x, pp, mb, H264Const.identityMapping4);
                savePrediction8x8(mbX, x[0], 0);
                savePrediction8x8(mbX, x[1], 1);
            }

            decodeChromaSkip(refs, x, pp, mbX, mbY, mb);

            collectPredictors(mb, mbX);

            saveMvs(x, mbX, mbY);
            mbTypes[mbAddr] = topMBType[mbX] = leftMBType = null;
            mbQps[0][mbAddr] = qp;
            mbQps[1][mbAddr] = calcQpChroma(qp, chromaQpOffset[0]);
            mbQps[2][mbAddr] = calcQpChroma(qp, chromaQpOffset[1]);
        }

        public void predictBDirect(Frame[][] refs, int mbX, int mbY, bool lAvb, bool tAvb, bool tlAvb,
                bool trAvb, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] pp, Picture mb, int[] blocks)
        {
            if (sh.direct_spatial_mv_pred_flag)
                predictBSpatialDirect(refs, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, x, pp, mb, blocks);
            else
                predictBTemporalDirect(refs, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, x, pp, mb, blocks);
        }

        private void predictBTemporalDirect(Frame[][] refs, int mbX, int mbY, bool lAvb, bool tAvb, bool tlAvb,
                bool trAvb, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] pp, Picture mb, int[] blocks8x8)
        {

            Picture mb0 = Picture.Create(16, 16, chromaFormat), mb1 = Picture.Create(16, 16, chromaFormat);
            foreach (var blk8x8 in blocks8x8)
            {
                int blk4x4_0 = H264Const.BLK8x8_BLOCKS[blk8x8][0];
                pp[blk8x8] = nVideo.Codecs.H264.H264Const.PartPred.Bi;

                if (!activeSps.direct_8x8_inference_flag)
                    foreach (var blk4x4 in H264Const.BLK8x8_BLOCKS[blk8x8])
                    {
                        predTemp4x4(refs, mbX, mbY, x, blk4x4);

                        int blkIndX = blk4x4 & 3;
                        int blkIndY = blk4x4 >> 2;

                        debugPrint("DIRECT_4x4 [" + blkIndY + ", " + blkIndX + "]: (" + x[0][blk4x4][0] + ","
                                + x[0][blk4x4][1] + "," + x[0][blk4x4][2] + "), (" + x[1][blk4x4][0] + ","
                                + x[1][blk4x4][1] + "," + x[1][blk4x4][2] + ")");

                        int blkPredX = (mbX << 6) + (blkIndX << 4);
                        int blkPredY = (mbY << 6) + (blkIndY << 4);

                        BlockInterpolator.getBlockLuma(refs[0][x[0][blk4x4][2]], mb0, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX
                                + x[0][blk4x4][0], blkPredY + x[0][blk4x4][1], 4, 4);
                        BlockInterpolator.getBlockLuma(refs[1][0], mb1, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX
                                + x[1][blk4x4][0], blkPredY + x[1][blk4x4][1], 4, 4);
                    }
                else
                {
                    int blk4x4Pred = H264Const.BLK_INV_MAP[blk8x8 * 5];
                    predTemp4x4(refs, mbX, mbY, x, blk4x4Pred);
                    propagatePred(x, blk8x8, blk4x4Pred);

                    int blkIndX = blk4x4_0 & 3;
                    int blkIndY = blk4x4_0 >> 2;

                    debugPrint("DIRECT_8x8 [" + blkIndY + ", " + blkIndX + "]: (" + x[0][blk4x4_0][0] + ","
                            + x[0][blk4x4_0][1] + "," + x[0][blk4x4_0][2] + "), (" + x[1][blk4x4_0][0] + ","
                            + x[1][blk4x4_0][1] + "," + x[0][blk4x4_0][2] + ")");

                    int blkPredX = (mbX << 6) + (blkIndX << 4);
                    int blkPredY = (mbY << 6) + (blkIndY << 4);

                    BlockInterpolator.getBlockLuma(refs[0][x[0][blk4x4_0][2]], mb0, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX
                            + x[0][blk4x4_0][0], blkPredY + x[0][blk4x4_0][1], 8, 8);
                    BlockInterpolator.getBlockLuma(refs[1][0], mb1, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX
                            + x[1][blk4x4_0][0], blkPredY + x[1][blk4x4_0][1], 8, 8);
                }
                prediction.mergePrediction(x[0][blk4x4_0][2], x[1][blk4x4_0][2], nVideo.Codecs.H264.H264Const.PartPred.Bi, 0, mb0.getPlaneData(0),
                        mb1.getPlaneData(0), H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], 16, 8, 8, mb.getPlaneData(0), refs, thisFrame);
            }
        }

        private void predTemp4x4(Frame[][] refs, int mbX, int mbY, int[][][] x, int blk4x4)
        {
            int mbWidth = activeSps.pic_width_in_mbs_minus1 + 1;

            Frame picCol = refs[1][0];
            int blkIndX = blk4x4 & 3;
            int blkIndY = blk4x4 >> 2;

            int blkPosX = (mbX << 2) + blkIndX;
            int blkPosY = (mbY << 2) + blkIndY;

            int[] mvCol = picCol.getMvs()[0][blkPosY][blkPosX];
            Frame refL0;
            int refIdxL0;
            if (mvCol[2] == -1)
            {
                mvCol = picCol.getMvs()[1][blkPosY][blkPosX];
                if (mvCol[2] == -1)
                {
                    refIdxL0 = 0;
                    refL0 = refs[0][0];
                }
                else
                {
                    refL0 = picCol.getRefsUsed()[mbY * mbWidth + mbX][1][mvCol[2]];
                    refIdxL0 = findPic(refs[0], refL0);
                }
            }
            else
            {
                refL0 = picCol.getRefsUsed()[mbY * mbWidth + mbX][0][mvCol[2]];
                refIdxL0 = findPic(refs[0], refL0);
            }

            x[0][blk4x4][2] = refIdxL0;
            x[1][blk4x4][2] = 0;

            int td = CABAC.clip(picCol.getPOC() - refL0.getPOC(), -128, 127);
            if (!refL0.isShortTerm() || td == 0)
            {
                x[0][blk4x4][0] = mvCol[0];
                x[0][blk4x4][1] = mvCol[1];
                x[1][blk4x4][0] = 0;
                x[1][blk4x4][1] = 0;
            }
            else
            {
                int tb = CABAC.clip(thisFrame.getPOC() - refL0.getPOC(), -128, 127);
                int tx = (16384 + Math.Abs(td / 2)) / td;
                int dsf = CABAC.clip((tb * tx + 32) >> 6, -1024, 1023);

                x[0][blk4x4][0] = (dsf * mvCol[0] + 128) >> 8;
                x[0][blk4x4][1] = (dsf * mvCol[1] + 128) >> 8;
                x[1][blk4x4][0] = (x[0][blk4x4][0] - mvCol[0]);
                x[1][blk4x4][1] = (x[0][blk4x4][1] - mvCol[1]);
            }
        }

        private int findPic(Frame[] frames, Frame refL0)
        {
            for (int i = 0; i < frames.Length; i++)
                if (frames[i] == refL0)
                    return i;
            //Logger.error("RefPicList0 shall contain refPicCol");
            return 0;
        }

        private void predictBSpatialDirect(Frame[][] refs, int mbX, int mbY, bool lAvb, bool tAvb, bool tlAvb,
                bool trAvb, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] pp, Picture mb, int[] blocks8x8)
        {

            int[] a0 = mvLeft[0][0], a1 = mvLeft[1][0];
            int[] b0 = mvTop[0][mbX << 2], b1 = mvTop[1][mbX << 2];
            int[] c0 = mvTop[0][(mbX << 2) + 4], c1 = mvTop[1][(mbX << 2) + 4];
            int[] d0 = mvTopLeft[0];
            int[] d1 = mvTopLeft[1];

            int refIdxL0 = calcRef(a0, b0, c0, d0, lAvb, tAvb, tlAvb, trAvb, mbX);
            int refIdxL1 = calcRef(a1, b1, c1, d1, lAvb, tAvb, tlAvb, trAvb, mbX);

            Picture mb0 = Picture.Create(16, 16, chromaFormat), mb1 = Picture.Create(16, 16, chromaFormat);

            if (refIdxL0 < 0 && refIdxL1 < 0)
            {
                foreach (var blk8x8 in blocks8x8)
                {
                    foreach (var blk4x4 in H264Const.BLK8x8_BLOCKS[blk8x8])
                    {
                        x[0][blk4x4][0] = x[0][blk4x4][1] = x[0][blk4x4][2] = x[1][blk4x4][0] = x[1][blk4x4][1] = x[1][blk4x4][2] = 0;
                    }
                    pp[blk8x8] = nVideo.Codecs.H264.H264Const.PartPred.Bi;

                    int blkOffX = (blk8x8 & 1) << 5;
                    int blkOffY = (blk8x8 >> 1) << 5;
                    BlockInterpolator.getBlockLuma(refs[0][0], mb0, H264Const.BLK_8x8_MB_OFF_LUMA[blk8x8], (mbX << 6) + blkOffX,
                            (mbY << 6) + blkOffY, 8, 8);
                    BlockInterpolator.getBlockLuma(refs[1][0], mb1, H264Const.BLK_8x8_MB_OFF_LUMA[blk8x8], (mbX << 6) + blkOffX,
                            (mbY << 6) + blkOffY, 8, 8);
                    prediction.mergePrediction(0, 0, nVideo.Codecs.H264.H264Const.PartPred.Bi, 0, mb0.getPlaneData(0), mb1.getPlaneData(0),
                            H264Const.BLK_8x8_MB_OFF_LUMA[blk8x8], 16, 8, 8, mb.getPlaneData(0), refs, thisFrame);
                    debugPrint("DIRECT_8x8 [" + (blk8x8 & 2) + ", " + ((blk8x8 << 1) & 2) + "]: (0,0,0), (0,0,0)");
                }
                return;
            }
            int mvX0 = calcMVPredictionMedian(a0, b0, c0, d0, lAvb, tAvb, trAvb, tlAvb, refIdxL0, 0);
            int mvY0 = calcMVPredictionMedian(a0, b0, c0, d0, lAvb, tAvb, trAvb, tlAvb, refIdxL0, 1);
            int mvX1 = calcMVPredictionMedian(a1, b1, c1, d1, lAvb, tAvb, trAvb, tlAvb, refIdxL1, 0);
            int mvY1 = calcMVPredictionMedian(a1, b1, c1, d1, lAvb, tAvb, trAvb, tlAvb, refIdxL1, 1);

            Frame col = refs[1][0];
            nVideo.Codecs.H264.H264Const.PartPred partPred = refIdxL0 >= 0 && refIdxL1 >= 0 ? nVideo.Codecs.H264.H264Const.PartPred.Bi : (refIdxL0 >= 0 ? nVideo.Codecs.H264.H264Const.PartPred.L0 : nVideo.Codecs.H264.H264Const.PartPred.L1);
            foreach (var blk8x8 in blocks8x8)
            {
                int blk4x4_0 = H264Const.BLK8x8_BLOCKS[blk8x8][0];

                if (!activeSps.direct_8x8_inference_flag)
                    foreach (var blk4x4 in H264Const.BLK8x8_BLOCKS[blk8x8])
                    {
                        pred4x4(mbX, mbY, x, pp, refIdxL0, refIdxL1, mvX0, mvY0, mvX1, mvY1, col, partPred, blk4x4);

                        int blkIndX = blk4x4 & 3;
                        int blkIndY = blk4x4 >> 2;

                        debugPrint("DIRECT_4x4 [" + blkIndY + ", " + blkIndX + "]: (" + x[0][blk4x4][0] + ","
                                + x[0][blk4x4][1] + "," + refIdxL0 + "), (" + x[1][blk4x4][0] + "," + x[1][blk4x4][1] + ","
                                + refIdxL1 + ")");

                        int blkPredX = (mbX << 6) + (blkIndX << 4);
                        int blkPredY = (mbY << 6) + (blkIndY << 4);

                        if (refIdxL0 >= 0)
                            BlockInterpolator.getBlockLuma(refs[0][refIdxL0], mb0, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX
                                    + x[0][blk4x4][0], blkPredY + x[0][blk4x4][1], 4, 4);
                        if (refIdxL1 >= 0)
                            BlockInterpolator.getBlockLuma(refs[1][refIdxL1], mb1, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX
                                    + x[1][blk4x4][0], blkPredY + x[1][blk4x4][1], 4, 4);
                    }
                else
                {
                    int blk4x4Pred = H264Const.BLK_INV_MAP[blk8x8 * 5];
                    pred4x4(mbX, mbY, x, pp, refIdxL0, refIdxL1, mvX0, mvY0, mvX1, mvY1, col, partPred, blk4x4Pred);
                    propagatePred(x, blk8x8, blk4x4Pred);

                    int blkIndX = blk4x4_0 & 3;
                    int blkIndY = blk4x4_0 >> 2;

                    debugPrint("DIRECT_8x8 [" + blkIndY + ", " + blkIndX + "]: (" + x[0][blk4x4_0][0] + ","
                            + x[0][blk4x4_0][1] + "," + refIdxL0 + "), (" + x[1][blk4x4_0][0] + "," + x[1][blk4x4_0][1]
                            + "," + refIdxL1 + ")");

                    int blkPredX = (mbX << 6) + (blkIndX << 4);
                    int blkPredY = (mbY << 6) + (blkIndY << 4);

                    if (refIdxL0 >= 0)
                        BlockInterpolator.getBlockLuma(refs[0][refIdxL0], mb0, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX
                                + x[0][blk4x4_0][0], blkPredY + x[0][blk4x4_0][1], 8, 8);
                    if (refIdxL1 >= 0)
                        BlockInterpolator.getBlockLuma(refs[1][refIdxL1], mb1, H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX
                                + x[1][blk4x4_0][0], blkPredY + x[1][blk4x4_0][1], 8, 8);
                }
                prediction.mergePrediction(x[0][blk4x4_0][2], x[1][blk4x4_0][2], refIdxL0 >= 0 ? (refIdxL1 >= 0 ? nVideo.Codecs.H264.H264Const.PartPred.Bi : nVideo.Codecs.H264.H264Const.PartPred.L0)
                        : nVideo.Codecs.H264.H264Const.PartPred.L1, 0, mb0.getPlaneData(0), mb1.getPlaneData(0), H264Const.BLK_4x4_MB_OFF_LUMA[blk4x4_0], 16, 8, 8, mb
                        .getPlaneData(0), refs, thisFrame);
            }
        }

        private int calcRef(int[] a0, int[] b0, int[] c0, int[] d0, bool lAvb, bool tAvb, bool tlAvb,
                bool trAvb, int mbX)
        {
            return minPos(minPos(lAvb ? a0[2] : -1, tAvb ? b0[2] : -1), trAvb ? c0[2] : (tlAvb ? d0[2] : -1));
        }

        private void propagatePred(int[][][] x, int blk8x8, int blk4x4Pred)
        {
            int b0 = H264Const.BLK8x8_BLOCKS[blk8x8][0];
            int b1 = H264Const.BLK8x8_BLOCKS[blk8x8][1];
            int b2 = H264Const.BLK8x8_BLOCKS[blk8x8][2];
            int b3 = H264Const.BLK8x8_BLOCKS[blk8x8][3];
            x[0][b0][0] = x[0][b1][0] = x[0][b2][0] = x[0][b3][0] = x[0][blk4x4Pred][0];
            x[0][b0][1] = x[0][b1][1] = x[0][b2][1] = x[0][b3][1] = x[0][blk4x4Pred][1];
            x[0][b0][2] = x[0][b1][2] = x[0][b2][2] = x[0][b3][2] = x[0][blk4x4Pred][2];
            x[1][b0][0] = x[1][b1][0] = x[1][b2][0] = x[1][b3][0] = x[1][blk4x4Pred][0];
            x[1][b0][1] = x[1][b1][1] = x[1][b2][1] = x[1][b3][1] = x[1][blk4x4Pred][1];
            x[1][b0][2] = x[1][b1][2] = x[1][b2][2] = x[1][b3][2] = x[1][blk4x4Pred][2];
        }

        private void pred4x4(int mbX, int mbY, int[][][] x, nVideo.Codecs.H264.H264Const.PartPred[] pp, int refL0, int refL1, int mvX0, int mvY0,
                int mvX1, int mvY1, Frame col, nVideo.Codecs.H264.H264Const.PartPred partPred, int blk4x4)
        {
            int blkIndX = blk4x4 & 3;
            int blkIndY = blk4x4 >> 2;

            int blkPosX = (mbX << 2) + blkIndX;
            int blkPosY = (mbY << 2) + blkIndY;

            x[0][blk4x4][2] = refL0;
            x[1][blk4x4][2] = refL1;

            int[] mvCol = col.getMvs()[0][blkPosY][blkPosX];
            if (mvCol[2] == -1)
                mvCol = col.getMvs()[1][blkPosY][blkPosX];

            bool colZero = col.isShortTerm() && mvCol[2] == 0 && (Math.Abs(mvCol[0]) >> 1) == 0 && (Math.Abs(mvCol[1]) >> 1) == 0;

            if (refL0 > 0 || !colZero)
            {
                x[0][blk4x4][0] = mvX0;
                x[0][blk4x4][1] = mvY0;
            }
            if (refL1 > 0 || !colZero)
            {
                x[1][blk4x4][0] = mvX1;
                x[1][blk4x4][1] = mvY1;
            }

            pp[H264Const.BLK_8x8_IND[blk4x4]] = partPred;
        }

        private int minPos(int a, int b)
        {
            return a >= 0 && b >= 0 ? Math.Min(a, b) : Math.Max(a, b);
        }

        public void predictPSkip(Frame[][] refs, int mbX, int mbY, bool lAvb, bool tAvb, bool tlAvb,
                bool trAvb, int[][][] x, Picture mb)
        {
            int mvX = 0, mvY = 0;
            if (lAvb && tAvb)
            {
                int[] b = mvTop[0][mbX << 2];
                int[] a = mvLeft[0][0];

                if ((a[0] != 0 || a[1] != 0 || a[2] != 0) && (b[0] != 0 || b[1] != 0 || b[2] != 0))
                {
                    mvX = calcMVPredictionMedian(a, b, mvTop[0][(mbX << 2) + 4], mvTopLeft[0], lAvb, tAvb, trAvb, tlAvb, 0,
                            0);
                    mvY = calcMVPredictionMedian(a, b, mvTop[0][(mbX << 2) + 4], mvTopLeft[0], lAvb, tAvb, trAvb, tlAvb, 0,
                            1);
                }
            }
            debugPrint("MV_SKIP: (" + mvX + "," + mvY + ")");
            int blk8x8X = mbX << 1;
            predModeLeft[0] = predModeLeft[1] = predModeTop[blk8x8X] = predModeTop[blk8x8X + 1] = nVideo.Codecs.H264.H264Const.PartPred.L0;

            int xx = mbX << 2;
            copyVect(mvTopLeft[0], mvTop[0][xx + 3]);
            saveVect(mvTop[0], xx, xx + 4, mvX, mvY, 0);
            saveVect(mvLeft[0], 0, 4, mvX, mvY, 0);

            for (int i = 0; i < 16; i++)
            {
                x[0][i][0] = mvX;
                x[0][i][1] = mvY;
                x[0][i][2] = 0;
            }
            BlockInterpolator.getBlockLuma(refs[0][0], mb, 0, (mbX << 6) + mvX, (mbY << 6) + mvY, 16, 16);

            prediction.mergePrediction(0, 0, nVideo.Codecs.H264.H264Const.PartPred.L0, 0, mb.getPlaneData(0), null, 0, 16, 16, 16, mb.getPlaneData(0), refs,
                    thisFrame);
        }

        public void decodeChromaSkip(Frame[][] reference, int[][][] vectors, nVideo.Codecs.H264.H264Const.PartPred[] pp, int mbX, int mbY, Picture mb)
        {
            predictChromaInter(reference, vectors, mbX << 3, mbY << 3, 1, mb, pp);
            predictChromaInter(reference, vectors, mbX << 3, mbY << 3, 2, mb, pp);
        }

        private void debugPrint(String str)
        {
            //if (debug)
            //Logger.debug(str);
        }

        public void setDebug(bool debug)
        {
            this.debug = debug;
        }
    }
}
